1
0
mirror of https://gitlab.crans.org/bde/nk20-scripts synced 2025-07-03 03:12:47 +02:00

Compare commits

52 Commits

Author SHA1 Message Date
abd5af9ad2 borg backup 2024-08-10 19:03:29 +02:00
472c9c33ce Update extract_ml_registrations.py 2024-06-04 00:16:36 +02:00
6149f11e53 Update extract_ml_registrations.py 2024-06-03 23:01:42 +02:00
08455e6e60 Update extract_ml_registrations.py 2024-06-03 22:58:48 +02:00
b17780e5e9 Update extract_ml_registrations.py 2024-06-03 22:55:41 +02:00
354a1f845e Update backup script
Remove useless tee, use "mkdir -p" and use .sql.gz.
2023-08-31 12:21:48 +02:00
f580f9b9e9 Merge branch 'better_anonymization' into 'master'
anonymize more data

See merge request bde/nk20-scripts!4
2023-07-16 17:13:14 +02:00
d7715fa81a Merge branch 'add_year_tag' into 'master'
Extraction ML Adhérents des années N et N-years

See merge request bde/nk20-scripts!3
2023-07-14 20:23:33 +02:00
81e90fa430 add a flag to choose data to anonymize (all, wei, user visible) and verbosity 2023-07-13 21:53:54 +02:00
11bcc07bf4 fix date reading and a variable name 2023-07-13 20:06:57 +02:00
c518b3dddb Merge branch 'l_eveil_du_nanax' into 'master'
L'eveil du nanax

See merge request bde/nk20-scripts!2
2023-07-13 19:48:50 +02:00
a965ab913c anonymize more data 2023-07-13 19:43:47 +02:00
4471307b37 Ignore club notes that are used by the BDE for particular events 2023-04-06 17:57:32 +02:00
c69c5197c9 Extraction ML Adhérents des années N et N-years 2023-03-19 01:28:55 +01:00
c4f128786d De l'inclusif, partout
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 13:18:58 +02:00
861f03eb6d [scripts] Remove README and add link to documentation
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:54:27 +02:00
48d9a8b5d2 Replace ... by …
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:17:17 +02:00
86bc2d2698 Add space before line breaks in Wiki export of activities
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-12-13 17:30:02 +01:00
7a022b9407 Update copyright for 2021
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-06-14 21:45:35 +02:00
3442edd2bf Reorder imports and fix trailing spaces 2021-05-12 17:43:18 +02:00
1e9d731715 Fix minimum amount for the send_mail_to_negative_balances script
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-27 09:52:30 +02:00
0c7070aea1 Send mail to admins if a user got deleted iff it was successfully deleted
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-22 18:33:20 +02:00
961365656c Compile Javascript translations in STATIC_ROOT directory
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-22 16:20:24 +02:00
076e1f0013 Mails are sent by the cron, not by the script
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-22 15:45:15 +02:00
f8feff7c55 Prevent data deletion in the anonymization script
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-22 15:40:10 +02:00
0fc9c4c50e In the force delete script, delete transactions transaction by transaction
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-22 15:13:14 +02:00
5ce65e36a8 In the mail that logs negative balances, add option to log old members
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-14 15:33:06 +02:00
cf8b05d20a Adapt verbosity of some scripts
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-14 15:18:24 +02:00
13322189dc Update last report date only in non-debug mode
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-08 17:38:25 +02:00
7676f69216 Fix note list when daily reports are sent
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-04-08 17:33:16 +02:00
8ec7d68a16 Add script to force delete a user, in case of duplicates
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-02-22 11:54:19 +01:00
dbe7bf6591 Export JS translation files as static files 2020-11-16 00:29:26 +01:00
654492f9e9 The note account must be active in order to have access to the Rest Framework API 2020-10-20 10:30:38 +02:00
84be9d0062 Note account has a special treatment in potential future NK15 import (compatibility commit) 2020-10-20 00:19:33 +02:00
7e27c3b71b Backups are sent to Zamok 2020-09-08 13:16:03 +02:00
0107dd0a94 Refactor the script to extract the mails that are registered to an events mailing list 2020-09-08 10:11:08 +02:00
e5b76b7c35 Linebreaks are rendered as <<BR>> in the wiki 2020-09-07 13:54:03 +02:00
4506dd4dc0 Plain text mode in reports 2020-09-07 11:02:10 +02:00
bac22dcbac Add __str__ to models, remove null=True in CharField and TextField 2020-09-07 01:06:22 +02:00
4f5a794798 Fix refresh activities cron 2020-09-05 14:28:02 +02:00
69c5c3bb36 Save the list of changed usernames and lost aliases 2020-09-05 13:50:57 +02:00
7479671b3f Don't rebuild systematically migrations 2020-09-05 10:07:20 +02:00
7246f4d18a Change debug option to "print stdout" / "edit wiki" in the Refresh activities script 2020-09-05 00:45:10 +02:00
2a113d22b9 I broke the import script 2020-09-05 00:33:38 +02:00
525f091b0c Test activity app 2020-09-04 21:46:40 +02:00
4e1bcd1808 Send user id and group id in Docker bash 2020-09-02 22:51:59 +02:00
1145f75a96 Add script to launch a Docker bash easily 2020-09-02 15:26:36 +02:00
c1c0a87971 RecurrentTransaction has no longer a category 2020-09-01 15:54:32 +02:00
2b1c05ff98 Prevent also club owners when the note balance is negative 2020-08-31 16:13:23 +02:00
4179cad611 When data is imported from the NK15, prevent users whenever some aliases are deleted 2020-08-24 12:41:51 +02:00
81709539a2 Replace timezone.now().date() by date.today() 2020-08-16 00:35:11 +02:00
2495128755 🐛 Last report date is a datetime, not a date 2020-08-09 15:53:47 +02:00
26 changed files with 589 additions and 209 deletions

1
.gitignore vendored
View File

@ -33,6 +33,7 @@ coverage
# Local data
secrets.py
*/.env_borg
*.log
# Virtualenv

View File

@ -1,63 +1,3 @@
# Script de la NoteKfet 2020
## Commandes Django
> les commandes sont documentées:
> `./manage.py command --help`
- `import_nk15` :
Importe un dump de la NoteKfet 2015.
- `make_su [--STAFF|-s] [--SUPER|-S]` :
Rend actifs les pseudos renseignés.
* Si `--STAFF` ou `-s` est renseigné, donne en plus le statut d'équipe aux pseudos renseignés,
permettant l'accès à l'interface admin.
* Si `--SUPER` ou `-S` est renseigné, donne en plus le statut de super-utilisateur aux pseudos renseignés,
octroyant tous les droits sur la plateforme.
- `wei_algorithm` :
Lance l'algorithme de répartition des 1A au dernier WEI. Cela a pour effet de suggérer un bus pour tous les 1A
inscrits au dernier WEI en fonction des données rentrées dans le sondage, la validation se faisant ensuite
manuellement via l'interface Web.
- `extract_ml_registrations --type {members, clubs, events, art, sport} [--year|-y YEAR]` :
Récupère la liste des adresses mail à inscrire à une liste de diffusion donnée.
* `members` : Liste des adresses mail des utilisateurs ayant une adhésion BDE (et non Kfet) active.
* `clubs` : Liste des adresses mail de contact de tous les clubs BDE enregistrés.
* `events` : Liste de toutes les adresses mails des utilisateurs inscrits au WEI ayant demandé à s'inscrire sur
la liste de diffusion des événements du BDE.
* `art` : Liste de toutes les adresses mails des utilisateurs inscrits au WEI ayant demandé à s'inscrire sur
la liste de diffusion concertnant les actualités artistiques du BDA.
* `sport` : Liste de toutes les adresses mails des utilisateurs inscrits au WEI ayant demandé à s'inscrire sur
la liste de diffusion concertnant les actualités sportives du BDS.
Le champ `--year` est optionnel : il permet de choisir l'année du WEI en question (pour les trois dernières
options). Si non renseigné, il s'agit du dernier WEI.
Par défaut, si `--type` est non renseigné, la liste des adhérents BDE est renvoyée.
- `extract_wei_registrations [--year|-y YEAR] [--bus|-b BUS] [--team|-t TEAM] [--sep SEP]` :
Récupère la liste des inscriptions au WEI et l'exporte au format CSV. Arguments possibles, optionnels :
* `--year YEAR` : sélectionne l'année du WEI. Par défaut, il s'agit du dernier WEI ayant eu lieu.
* `--bus BUS` : filtre par bus, en récupérant uniquement les inscriptions sur un bus. Par défaut, on affiche
tous les bus.
* `--team TEAM` : filtre par équipe, en récupérant uniquement les inscriptions sur une équipe. Par défaut, on
affiche toutes les équipes. Entrer `"none"` filtre les inscriptions sans équipe (chefs de bus, ...)
* `--sep` : définit le caractère de séparation des colonnes du fichier CSV. Par défaut, il s'agit du caractère `|`.
Merci de ne pas rentrer plus d'un caractère.
## Shell
- Tabula rasa :
```shell script
sudo -u postgres sh -c "dropdb note_db && psql -c 'CREATE DATABASE note_db OWNER note;'"
```
La documentation est disponible sur <https://note.crans.org/doc/scripts/>.

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'scripts.apps.ScriptsConfig'

View File

@ -1,8 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
from django.core.signals import got_request_exception
class ScriptsConfig(AppConfig):

View File

@ -1,14 +1,13 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import json
import time
from collections import defaultdict
from django.core.management.base import BaseCommand
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db import transaction
from polymorphic.models import PolymorphicModel
@ -16,6 +15,7 @@ def timed(method):
""""
A simple decorator to measure time elapsed in class function (hence the args[0])
"""
def _timed(*args, **kw):
ts = time.time()
result = method(*args, **kw)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.management.base import BaseCommand
@ -7,18 +7,87 @@ from django.db import connection
class Command(BaseCommand):
"""
Command to protect sensitive data during the beta phase, to prevent a right escalation.
Phone number, email address, postal address, first and last name are removed.
Command to protect sensitive data during the beta phase or after WEI.
Phone number, email address, postal address, first and last name,
IP addresses, health issues, gender and birth date are removed.
"""
def handle(self, *args, **kwargs):
def add_arguments(self, parser):
parser.add_argument('--force', '-f', action='store_true', help="Actually anonymize data.")
parser.add_argument('--type', '-t', choices=["all", "wei", "user"], default="",
help='Select the type of data to anonyze (default None)')
def handle(self, *args, **options):
if not options['force']:
if options['type'] == "all":
self.stderr.write("CAUTION: This is a dangerous script. This will reset ALL personal data with "
"sample data. Don't use in production! If you know what you are doing, please "
"add --force option.")
elif options['type'] == "wei":
self.stderr.write("CAUTION: This is a dangerous script. This will reset WEI personal data with "
"sample data. Use it in production only after WEI. If you know what you are doing,"
"please add --force option.")
elif options['type'] == "user":
self.stderr.write("CAUTION: This is a dangerous script. This will reset all personal data "
"visible by user (not admin or trez BDE) with sample data. Don't use in "
"production! If you know what you are doing, please add --force option.")
else:
self.stderr.write("CAUTION: This is a dangerous script. This will reset all personal data with "
"sample data. Don't use in production ('wei' can be use in production after "
"the WEI)! If you know what you are doing, please choose a type.")
exit(1)
cur = connection.cursor()
cur.execute("UPDATE member_profile SET "
if options['type'] in ("all","user"):
if options['verbosity'] != 0:
self.stdout.write("Anonymize profile, user club and guest data")
cur.execute("UPDATE member_profile SET "
"phone_number = '0123456789', "
"address = '4 avenue des Sciences, 91190 GIF-SUR-YVETTE';")
cur.execute("UPDATE auth_user SET "
cur.execute("UPDATE auth_user SET "
"first_name = 'Anne', "
"last_name = 'Onyme', "
"email = 'anonymous@example.com';")
cur.execute("UPDATE member_club SET "
cur.execute("UPDATE member_club SET "
"email = 'anonymous@example.com';")
cur.execute("UPDATE activity_guest SET "
"first_name = 'Anne', "
"last_name = 'Onyme';")
if options['type'] in ("all","wei","user"):
if options['verbosity'] != 0:
self.stdout.write("Anonymize WEI data")
cur.execute("UPDATE wei_weiregistration SET "
"birth_date = '1998-01-08', "
"emergency_contact_name = 'Anne Onyme', "
"emergency_contact_phone = '0123456789', "
"gender = 'nonbinary', "
"health_issues = 'Tout va bien';")
if options['type'] == "all":
if options['verbosity'] != 0:
self.stdout.write("Anonymize invoice, special transaction, log, mailer and oauth data")
cur.execute("UPDATE treasury_invoice SET "
"name = 'Anne Onyme', "
"object = 'Rends nous riches', "
"description = 'Donne nous plein de sous', "
"address = '4 avenue des Sciences, 91190 GIF-SUR-YVETTE';")
cur.execute("UPDATE treasury_product SET "
"designation = 'un truc inutile';")
cur.execute("UPDATE note_specialtransaction SET "
"bank = 'le matelas', "
"first_name = 'Anne', "
"last_name = 'Onyme';")
cur.execute("UPDATE logs_changelog SET "
"ip = '127.0.0.1', "
"data = 'new data', "
"previous = 'old data';")
cur.execute("UPDATE mailer_messagelog SET "
"log_message = 'log message', "
"message_data = 'message data';")
cur.execute("UPDATE mailer_dontsendentry SET "
"to_address = 'anonymous@example.com';")
cur.execute("UPDATE oauth2_provider_application SET "
"name = 'external app', "
"redirect_uris = 'http://external.app', "
"client_secret = 'abcdefghijklmnopqrstuvwxyz';")
cur.close()

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.mail import send_mail
from django.core.management import BaseCommand
from django.db.models import Sum, F
@ -14,7 +14,6 @@ class Command(BaseCommand):
parser.add_argument('--check-all', '-a', action='store_true', help='Check all notes')
parser.add_argument('--check', '-c', type=int, nargs='+', help='Select note ids')
parser.add_argument('--fix', '-f', action='store_true', help='Fix note balances')
parser.add_argument('--mail', '-m', action='store_true', help='Send mail to admins if there is an error')
def handle(self, *args, **options):
error = False
@ -23,10 +22,11 @@ class Command(BaseCommand):
if options["sum_all"]:
s = Note.objects.aggregate(Sum("balance"))["balance__sum"]
if s:
err_log += self.style.NOTICE("LA SOMME DES NOTES NE VAUT PAS ZÉRO : " + pretty_money(s)) + "\n"
self.stderr.write(self.style.NOTICE("LA SOMME DES NOTES NE VAUT PAS ZÉRO : " + pretty_money(s)))
error = True
else:
self.stdout.write(self.style.SUCCESS("La somme des notes vaut bien zéro."))
if options["verbosity"] > 0:
self.stdout.write(self.style.SUCCESS("La somme des notes vaut bien zéro."))
notes = Note.objects.none()
if options["check_all"]:
@ -42,19 +42,13 @@ class Command(BaseCommand):
.annotate(total=F("quantity") * F("amount")).aggregate(Sum("total"))["total__sum"] or 0
calculated_balance = incoming - outcoming
if calculated_balance != balance:
err_log += self.style.NOTICE("LA SOMME DES TRANSACTIONS DE LA NOTE {} NE CORRESPOND PAS "
"AVEC LE MONTANT RÉEL".format(str(note))) + "\n"
err_log += self.style.NOTICE("Attendu : {}, calculé : {}"
.format(pretty_money(balance), pretty_money(calculated_balance))) + "\n"
self.stderr.write(self.style.NOTICE(f"LA SOMME DES TRANSACTIONS DE LA NOTE {note} NE CORRESPOND PAS "
"AVEC LE MONTANT RÉEL"))
self.stderr.write(self.style.NOTICE(f"Attendu : {pretty_money(balance)}, "
f"calculé : {pretty_money(calculated_balance)}"))
if options["fix"]:
note.balance = calculated_balance
note.save()
error = True
if error:
self.stderr.write(err_log)
if options["mail"]:
send_mail("[Note Kfet] La base de données n'est pas consistante", err_log,
"NoteKfet2020 <notekfet2020@crans.org>", ["respo-info.bde@lists.crans.org"])
exit(1 if error else 0)

View File

@ -0,0 +1,28 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils import translation
from django.views.i18n import JavaScriptCatalog
class Command(BaseCommand):
"""
Generate Javascript translation files
"""
def handle(self, *args, **kwargs):
for code, _ in settings.LANGUAGES:
if code == settings.LANGUAGE_CODE:
continue
if kwargs["verbosity"] > 0:
self.stdout.write(f"Generate {code} javascript localization file")
with translation.override(code):
resp = JavaScriptCatalog().get(None, packages="member+note")
if not os.path.isdir(settings.STATIC_ROOT + "/js/jsi18n"):
os.makedirs(settings.STATIC_ROOT + "/js/jsi18n")
with open(settings.STATIC_ROOT + f"/js/jsi18n/{code}.js", "wb") as f:
f.write(resp.content)

View File

@ -0,0 +1,66 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
from django.contrib.auth.models import User
from django.core.management import BaseCommand
from member.models import Club, Membership
class Command(BaseCommand):
help = "Get mailing list registrations from the last wei. " \
"Usage: manage.py extract_ml_registrations -t {events,art,sport} -l {fr, en} -y {0, 1, ...}. " \
"You can write this into a file with a pipe, then paste the document into your mail manager."
def add_arguments(self, parser):
parser.add_argument('--type', '-t', choices=["members", "clubs", "events", "art", "sport"], default="members",
help='Select the type of the mailing list (default members)')
parser.add_argument('--lang', '-l', type=str, choices=['fr', 'en'], default='fr',
help='Select the registred users of the ML of the given language. Useful only for the '
'events mailing list.')
parser.add_argument('--years', '-y', type=int, default=0,
help='Select the cumulative registred users of a membership from years ago. 0 means the current users')
def handle(self, *args, **options):
# TODO: Improve the mailing list extraction system, and link it automatically with Mailman.
if options['verbosity'] == 0:
# This is useless, but this what the user asked.
return
if options["type"] == "members":
today_date = date.today()
selected_date = date(today_date.year - options["years"], today_date.month, today_date.day)
for membership in Membership.objects.filter(
club__name="BDE",
date_start__lte=today_date,
date_end__gte=selected_date,
).all():
self.stdout.write(membership.user.email)
return
if options["type"] == "clubs":
for club in Club.objects.all():
self.stdout.write(club.email)
return
# Get the list of mails that want to be registered to the events mailing list.
# Don't filter to valid members, old members can receive these mails as long as they want.
if options["type"] == "events":
for user in User.objects.filter(profile__ml_events_registration=options["lang"]).all():
self.stdout.write(user.email)
return
if options["type"] == "art":
nb=0
for user in User.objects.filter(profile__ml_art_registration=True).all():
self.stdout.write(user.email)
nb+=1
self.stdout.write(str(nb))
return
if options["type"] == "sport":
for user in User.objects.filter(profile__ml_sport_registration=True).all():
self.stdout.write(user.email)
return

View File

@ -0,0 +1,176 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import getpass
from time import sleep
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import mail_admins
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from django.test import override_settings
from note.models import Alias, Transaction
class Command(BaseCommand):
"""
This script is used to force delete a user.
THIS IS ONLY ATTENDED TO BE USED TO DELETE FAKE ACCOUNTS THAT
WERE VALIDATED BY ERRORS. Please use data anonymization if you
want to block the account of a user.
"""
def add_arguments(self, parser):
parser.add_argument('user', type=str, nargs='+', help="User id to delete.")
parser.add_argument('--force', '-f', action='store_true',
help="Force the script to have low verbosity.")
parser.add_argument('--doit', '-d', action='store_true',
help="Don't ask for a final confirmation and commit modification. "
"This option should really be used carefully.")
def handle(self, *args, **kwargs):
force = kwargs['force']
if not force:
self.stdout.write(self.style.WARNING("This is a dangerous script. "
"Please use --force to indicate that you known what you are doing. "
"Nothing will be deleted yet."))
sleep(5)
# We need to know who to blame.
qs = User.objects.filter(note__alias__normalized_name=Alias.normalize(getpass.getuser()))
if not qs.exists():
self.stderr.write(self.style.ERROR("I don't know who you are. Please add your linux id as an alias of "
"your own account."))
exit(2)
executor = qs.get()
deleted_users = []
deleted = []
# Don't send mails during the process
with override_settings(EMAIL_BACKEND='django.core.mail.backends.dummy.EmailBackend'):
for user_id in kwargs['user']:
if user_id.isnumeric():
qs = User.objects.filter(pk=int(user_id))
if not qs.exists():
self.stderr.write(self.style.WARNING(f"User {user_id} was not found. Ignoring…"))
continue
user = qs.get()
else:
qs = Alias.objects.filter(normalized_name=Alias.normalize(user_id), note__noteuser__isnull=False)
if not qs.exists():
self.stderr.write(self.style.WARNING(f"User {user_id} was not found. Ignoring…"))
continue
user = qs.get().note.user
with transaction.atomic():
local_deleted = []
# Unlock note to enable modifications
if force and not user.note.is_active:
user.note.is_active = True
user.note.save()
# Deleting transactions
transactions = Transaction.objects.filter(Q(source=user.note) | Q(destination=user.note)).all()
local_deleted += list(transactions)
for tr in transactions:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Removing {tr}")
if force:
tr.delete()
# Deleting memberships
memberships = user.memberships.all()
local_deleted += list(memberships)
if kwargs['verbosity'] >= 1:
for membership in memberships:
self.stdout.write(f"Removing {membership}")
if force:
memberships.delete()
# Deleting aliases
alias_set = user.note.alias.all()
local_deleted += list(alias_set)
if kwargs['verbosity'] >= 1:
for alias in alias_set:
self.stdout.write(f"Removing alias {alias}")
if force:
alias_set.delete()
if 'activity' in settings.INSTALLED_APPS:
from activity.models import Guest, Entry
# Deleting activity entries
entries = Entry.objects.filter(Q(note=user.note) | Q(guest__inviter=user.note)).all()
local_deleted += list(entries)
if kwargs['verbosity'] >= 1:
for entry in entries:
self.stdout.write(f"Removing {entry}")
if force:
entries.delete()
# Deleting invited guests
guests = Guest.objects.filter(inviter=user.note).all()
local_deleted += list(guests)
if kwargs['verbosity'] >= 1:
for guest in guests:
self.stdout.write(f"Removing guest {guest}")
if force:
guests.delete()
if 'treasury' in settings.INSTALLED_APPS:
from treasury.models import SogeCredit
# Deleting soge credit
credits = SogeCredit.objects.filter(user=user).all()
local_deleted += list(credits)
if kwargs['verbosity'] >= 1:
for credit in credits:
self.stdout.write(f"Removing {credit}")
if force:
credits.delete()
# Deleting note
local_deleted.append(user.note)
if force:
user.note.delete()
if 'logs' in settings.INSTALLED_APPS:
from logs.models import Changelog
# Replace log executors by the runner of the script
Changelog.objects.filter(user=user).update(user=executor)
# Deleting profile
local_deleted.append(user.profile)
if force:
user.profile.delete()
# Finally deleting user
if force:
user.delete()
local_deleted.append(user)
# This script should really not be used.
if not kwargs['doit'] and not input('You are about to delete real user data. '
'Are you really sure that it is what you want? [y/N] ')\
.lower().startswith('y'):
self.stdout.write(self.style.ERROR("Aborted."))
exit(1)
if kwargs['verbosity'] >= 1:
self.stdout.write(self.style.SUCCESS(f"User {user} deleted."))
deleted_users.append(user)
deleted += local_deleted
if deleted_users:
message = f"Les utilisateur⋅rices {deleted_users} ont été supprimé⋅es par {executor}.\n\n"
message += "Ont été supprimés en conséquence les objets suivants :\n\n"
for obj in deleted:
message += f"{repr(obj)} (pk: {obj.pk})\n"
if force and kwargs['doit']:
mail_admins("Utilisateur⋅rices supprimés", message)

View File

@ -1,22 +1,19 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import psycopg2 as pg
import psycopg2.extras as pge
import datetime
import json
from django.utils.timezone import make_aware, now
import psycopg2 as pg
import psycopg2.extras as pge
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from note.models import Note, NoteUser, NoteClub
from note.models import Alias
from django.utils.timezone import make_aware
from member.models import Club, Profile
from note.models import Alias, Note, NoteClub, NoteUser
from ._import_utils import ImportCommand, BulkCreateManager, timed
from ._import_utils import BulkCreateManager, ImportCommand, timed
M_DURATION = 396
M_START = datetime.date(2019, 8, 1)
@ -33,6 +30,11 @@ MAP_IDBDE = {
# some Aliases have been created in the fixtures
ALIAS_SET = {a[0] for a in Alias.objects.all().values_list("normalized_name")}
# Some people might loose some aliases due to normalization. We warn them on them.
LOST_ALIASES = {}
# In some rare cases, the username might be in conflict with some others. We change them and warn the users.
CHANGED_USERNAMES = []
note_user_type = ContentType.objects.get(app_label="note", model="noteuser")
note_club_type = ContentType.objects.get(app_label="note", model="noteclub")
@ -85,9 +87,10 @@ class Command(ImportCommand):
pseudo = row["pseudo"]
pseudo_norm = Alias.normalize(pseudo)
self.update_line(idx, n, pseudo)
# clean pseudo (normalized pseudo must be unique)
if pseudo_norm in ALIAS_SET:
# clean pseudo (normalized pseudo must be unique and not empty)
if not pseudo_norm or pseudo_norm in ALIAS_SET:
pseudo = pseudo + str(row["idbde"])
CHANGED_USERNAMES.append((pk_note, row["idbde"], pseudo))
else:
ALIAS_SET.add(pseudo_norm)
# clean date
@ -95,7 +98,7 @@ class Command(ImportCommand):
"pk": pk_note,
"balance": row['solde'],
"last_negative": None,
"is_active": True,
"is_active": not row["bloque"],
"display_image": "pic/default.png",
}
if row["last_negatif"] is not None:
@ -107,6 +110,11 @@ class Command(ImportCommand):
else:
passwd_nk15 = ''
# Note account should have no password and be active
if int(row["idbde"]) == 3508:
passwd_nk15 = "ipbased$127.0.0.1"
row["bloque"] = False
if row["idbde"] not in MAP_IDBDE_PROMOTION:
# NK12 bug. Applying default values
MAP_IDBDE_PROMOTION[row["idbde"]] = {"promo": 2014,
@ -119,7 +127,7 @@ class Command(ImportCommand):
"first_name": row["prenom"],
"last_name": row["nom"],
"email": row["mail"],
"is_active": True, # temporary
"is_active": not row["bloque"],
"date_joined": make_aware(MAP_IDBDE_PROMOTION[row["idbde"]]["created_at"]),
}
profile_dict = {
@ -131,8 +139,8 @@ class Command(ImportCommand):
"registration_valid": True,
"email_confirmed": True,
"promotion": MAP_IDBDE_PROMOTION[row["idbde"]]["promo"],
"report_frequency": row["report_period"],
"last_report": make_aware(row["previous_report_date"]).date(),
"report_frequency": max(row["report_period"], 0),
"last_report": make_aware(row["previous_report_date"]),
}
note_dict["created_at"] = make_aware(MAP_IDBDE_PROMOTION[row["idbde"]]["created_at"])
note_dict["polymorphic_ctype"] = note_user_type
@ -202,11 +210,13 @@ class Command(ImportCommand):
pk_alias = Alias.objects.order_by('-id').first().id + 1
for idx, row in enumerate(cur):
alias_name = row["alias"]
alias_name = (alias_name[:252] + '...') if len(alias_name) > 255 else alias_name
alias_name = (alias_name[:254] + '') if len(alias_name) > 255 else alias_name
alias_norm = Alias.normalize(alias_name)
self.update_line(idx, n, alias_norm)
# clean pseudo (normalized pseudo must be unique)
if alias_norm in ALIAS_SET:
if not alias_norm or alias_norm in ALIAS_SET:
LOST_ALIASES.setdefault(MAP_IDBDE[row["idbde"]], [])
LOST_ALIASES[MAP_IDBDE[row["idbde"]]].append(alias_name)
continue
else:
ALIAS_SET.add(alias_norm)
@ -237,3 +247,7 @@ class Command(ImportCommand):
filename = kwargs["save"]
with open(filename, 'w') as fp:
json.dump(MAP_IDBDE, fp, sort_keys=True, indent=2)
with open(filename + ".changed-usernames", 'w') as fp:
json.dump(CHANGED_USERNAMES, fp, sort_keys=True, indent=2)
with open(filename + ".removed-aliases", 'w') as fp:
json.dump(LOST_ALIASES, fp, sort_keys=True, indent=2)

View File

@ -1,34 +1,32 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import psycopg2 as pg
import psycopg2.extras as pge
import datetime
import copy
from django.utils.timezone import make_aware
from activity.models import Activity, ActivityType, Entry, Guest
from django.db import transaction
from activity.models import ActivityType, Activity, Guest, Entry
from django.utils.timezone import make_aware
from member.models import Club
from note.models import Note, NoteUser
from ._import_utils import ImportCommand, BulkCreateManager, timed
from ._import_utils import BulkCreateManager, ImportCommand, timed
MAP_ACTIVITY = dict()
CLUB_RELOU = [
0, # BDE
4771, # Kataclist
5162, # Assurance BDE ?!
5164, # S & L
0, # BDE
4771, # Kataclist
5162, # Assurance BDE ?!
5164, # S & L
625, # Aspique
5154, # Frekens
3944, # DiskJok[ENS]
5153, # Monopo[list]
2351, # JdRM
2365, # Pot Vieux
5154, # Frekens
3944, # DiskJok[ENS]
5153, # Monopo[list]
2351, # JdRM
2365, # Pot Vieux
]
class Command(ImportCommand):
"""
Import command for Activities Base Data (Comptes, and Aliases)
@ -50,7 +48,7 @@ class Command(ImportCommand):
row["responsable"] = 3508
note = self.MAP_IDBDE[row["responsable"]]
if note == 6244:
# Licorne magique ne doit pas utiliser son compte club pour proposer des activités
# Licorne magique ne doit pas utiliser son compte club pour proposer des activités
note = Note.objects.get(pk=self.MAP_IDBDE[6524])
note = note.id
organizer = Club.objects.filter(name=row["signature"])

View File

@ -1,17 +1,17 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import subprocess
from django.core.management.base import BaseCommand
from django.core.management import call_command
from ._import_utils import ImportCommand
class Command(ImportCommand):
"""
Command for importing the database of NK15.
Need to be run by a user with a registered role in postgres for the database nk15.
Need to be run by a user with a registered role in postgres for the database nk15.
"""
def handle(self, *args, **kwargs):

View File

@ -1,31 +1,25 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import copy
import datetime
import re
import pytz
import psycopg2 as pg
import psycopg2.extras as pge
import pytz
import datetime
import copy
from activity.models import Entry, GuestTransaction
from django.contrib.auth.models import User
from django.utils.timezone import make_aware
from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from note.models import (TemplateCategory,
TransactionTemplate,
Transaction,
RecurrentTransaction,
SpecialTransaction,
MembershipTransaction,
)
from note.models import Note, NoteClub
from activity.models import Guest, GuestTransaction, Entry
from django.db import transaction
from django.utils.timezone import make_aware
from member.models import Membership
from treasury.models import Remittance, SpecialTransactionProxy, SogeCredit
from ._import_utils import ImportCommand, BulkCreateManager, timed
from note.models import (MembershipTransaction, Note, NoteClub,
RecurrentTransaction, SpecialTransaction,
TemplateCategory, Transaction, TransactionTemplate)
from treasury.models import Remittance, SogeCredit, SpecialTransactionProxy
from ._import_utils import BulkCreateManager, ImportCommand, timed
MAP_TRANSACTION = dict()
MAP_REMITTANCE = dict()
@ -102,12 +96,11 @@ class Command(ImportCommand):
def _basic_transaction(self, row, obj_dict, child_dict):
if len(row["description"]) > 255:
obj_dict["reason"] = obj_dict["reason"][:250] + "...)"
obj_dict["reason"] = obj_dict["reason"][:252] + ")"
return obj_dict, None, None
def _template_transaction(self, row, obj_dict, child_dict):
if self.buttons.get(row["description"]):
child_dict["category_id"] = self.buttons[row["description"]][1]
child_dict["template_id"] = self.buttons[row["description"]][0]
# elif self.categories.get(row["categorie"]):
# child_dict["category_id"] = self.categories[row["categorie"]]
@ -215,14 +208,14 @@ class Command(ImportCommand):
"amount": row["montant"],
"created_at": date,
"destination_alias": "",
"invalidity_reason": None,
"invalidity_reason": "",
"quantity": row["quantite"],
"reason": row["description"],
"source_alias": "",
"valid": row["valide"],
}
if len(obj_dict["reason"]) > 255:
obj_dict["reason"] = obj_dict["reason"][:252] + "..."
obj_dict["reason"] = obj_dict["reason"][:254] + ""
# for child transaction Models
child_dict = {"pk": pk_transaction}
ttype = row["type"]
@ -317,6 +310,12 @@ class Command(ImportCommand):
)
bulk_mgr.done()
# Note account has a different treatment
for m in Membership.objects.filter(user_username="note").all():
m.date_end = "3142-12-12"
m.roles.set([20]) # PC Kfet role
m.save()
@timed
@transaction.atomic
def import_remittances(self, cur, chunk_size):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.management.base import BaseCommand
@ -16,7 +16,11 @@ class Command(BaseCommand):
user = User.objects.get(username=uname)
user.is_active = True
if kwargs['STAFF']:
if kwargs['verbosity'] > 0:
self.stdout.write(f"Add {user} to staff users…")
user.is_staff = True
if kwargs['SUPER']:
if kwargs['verbosity'] > 0:
self.stdout.write(f"Add {user} to superusers…")
user.is_superuser = True
user.save()

View File

@ -1,19 +1,19 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from bs4 import BeautifulSoup
from django.core.management import BaseCommand
from django.urls import reverse
from django.utils import timezone
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from bs4 import BeautifulSoup
from activity.models import Activity
from django.core.management import BaseCommand
from django.urls import reverse
from django.utils import timezone
class Command(BaseCommand):
acl_header = "#acl NoteKfet2015:read,write,admin NoteKfet2020:read,write,admin All:read Default\n"
acl_header = "#acl NoteKfet2020:read,write,admin All:read Default\n"
warning_header = """## NE PAS ÉDITER CETTE PAGE MANUELLEMENT
## ELLE EST GÉNÉRÉE AUTOMATIQUEMENT PAR LA NOTE KFET 2020
@ -24,7 +24,7 @@ class Command(BaseCommand):
* Ne pas éditer cette page manuellement, toute modification sera annulée automatiquement.
* Pour annoncer un nouvel événement, rendez-vous sur {activities_url}
""".format(activities_url="https://" + os.getenv("NOTE_URL") + reverse("activity:activity_list"))
""".format(activities_url="https://" + os.getenv("NOTE_URL", "") + reverse("activity:activity_list"))
@staticmethod
def connection(url):
@ -100,7 +100,7 @@ class Command(BaseCommand):
title=act.name,
start=timezone.localtime(act.date_start).strftime("%Y-%m-%d %H:%M"),
end=timezone.localtime(act.date_end).strftime("%Y-%m-%d %H:%M"),
description=act.description,
description=act.description.replace("\r", "").replace("\n", " <<BR>>"),
club=act.organizer.name,
location=act.location,
)
@ -108,7 +108,7 @@ class Command(BaseCommand):
return "|| {start} || {title} || {description} || {club} || {location} ||".format(
title=act.name,
start=timezone.localtime(act.date_start).strftime("%d/%m/%Y"),
description=act.description,
description=act.description.replace("\r", "").replace("\n", " <<BR>>"),
club=act.organizer.name,
location=act.location,
)
@ -150,30 +150,31 @@ class Command(BaseCommand):
return page, header + body
@staticmethod
def refresh_raw_wiki_page(comment="refresh", debug=True):
def refresh_raw_wiki_page(comment="refresh", print_stdout=False, edit_wiki=False):
page, content = Command.get_raw_page()
if debug:
if print_stdout:
print(content)
else:
if edit_wiki:
Command.edit_wiki(page, content, comment)
@staticmethod
def refresh_human_readable_wiki_page(comment="refresh", debug=True):
def refresh_human_readable_wiki_page(comment="refresh", print_stdout=False, edit_wiki=False):
page, content = Command.get_human_readable_page()
if debug:
if print_stdout:
print(content)
else:
if edit_wiki:
Command.edit_wiki(page, content, comment)
def add_arguments(self, parser):
parser.add_argument("--human", "-H", action="store_true", help="Save human readable page")
parser.add_argument("--raw", "-r", action="store_true", help="Save raw page, for the calendar")
parser.add_argument("--comment", "-c", action="store", type=str, default="", help="Comment of the modification")
parser.add_argument("--debug", "-d", action="store_true", help="Don't commit to the wiki, render in stdout")
parser.add_argument("--stdout", "-o", action="store_true", help="Render the wiki page in stdout")
parser.add_argument("--wiki", "-w", action="store_true", help="Send modifications to the wiki")
def handle(self, *args, **options):
if options["raw"]:
Command.refresh_raw_wiki_page(options["comment"], options["debug"])
Command.refresh_raw_wiki_page(options["comment"], options["stdout"], options["wiki"])
if options["human"]:
Command.refresh_human_readable_wiki_page(options["comment"], options["debug"])
Command.refresh_human_readable_wiki_page(options["comment"], options["stdout"], options["wiki"])

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
@ -6,7 +6,6 @@ from datetime import timedelta
from django.core.management.base import BaseCommand
from django.db.models import Count
from django.utils import timezone
from note.models import RecurrentTransaction, TransactionTemplate
@ -23,7 +22,8 @@ class Command(BaseCommand):
for d in queryset.all():
button_id = d["template"]
button = TransactionTemplate.objects.get(pk=button_id)
self.stdout.write(self.style.WARNING("Highlight button {name} ({count:d} transactions)..."
.format(name=button.name, count=d["transaction_count"])))
if kwargs['verbosity'] > 0:
self.stdout.write(self.style.WARNING("Highlight button {name} ({count:d} transactions)..."
.format(name=button.name, count=d["transaction_count"])))
button.highlighted = True
button.save()

View File

@ -1,13 +1,14 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date, timedelta
from django.core.mail import send_mail
from django.core.management import BaseCommand
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import activate
from note.models import NoteUser
from note.models import Note
class Command(BaseCommand):
@ -15,13 +16,24 @@ class Command(BaseCommand):
parser.add_argument("--spam", "-s", action='store_true', help="Spam negative users")
parser.add_argument("--report", "-r", action='store_true', help="Report the list of negative users to admins")
parser.add_argument("--negative-amount", "-n", action='store', type=int, default=1000,
help="Maximum amount to be considered as very negative")
help="Maximum amount to be considered as very negative (inclusive)")
parser.add_argument("--add-years", "-y", action='store', type=int, default=0,
help="Add also people that have a negative balance since N years "
"(default to only active members)")
def handle(self, *args, **options):
activate('fr')
notes = NoteUser.objects.filter(
if options['negative_amount'] == 0:
# Don't log empty notes
options['negative_amount'] = 1
notes = Note.objects.filter(
Q(noteuser__user__memberships__date_end__gte=
date.today() - timedelta(days=int(365.25 * options['add_years'])))
| (Q(noteclub__isnull=False) & ~Q(noteclub__club__name__icontains='- BDE')),
balance__lte=-options["negative_amount"],
user__memberships__date_end__gte=timezone.now().date(),
is_active=True,
).order_by('balance').distinct().all()
if options["spam"]:

View File

@ -1,33 +1,38 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
from django.core.management import BaseCommand
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import activate
from note.models import NoteUser, Transaction
from note.tables import HistoryTable
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--notes', '-n', type=int, nargs='+', help='Select note ids')
parser.add_argument('--debug', '-d', action='store_true', help='Debug mode, print mails in stdout')
def handle(self, *args, **options):
activate('fr')
notes = NoteUser.objects.filter(
user__memberships__date_end__gte=timezone.now(),
user__profile__report_frequency__gt=0,
).distinct().all()
if 'notes' in options and options['notes']:
notes = NoteUser.objects.filter(pk__in=options['notes']).all()
else:
notes = NoteUser.objects.filter(
user__memberships__date_end__gte=timezone.now(),
user__profile__report_frequency__gt=0,
).distinct().all()
for note in notes:
now = timezone.now()
last_report = note.user.profile.last_report
delta = now.date() - last_report
delta = now.date() - last_report.date()
if delta.days < note.user.profile.report_frequency:
continue
note.user.profile.last_report = now.date()
note.user.profile.save()
if not options["debug"]:
note.user.profile.last_report = now
note.user.profile.save()
last_transactions = Transaction.objects.filter(
Q(source=note) | Q(destination=note),
created_at__gte=last_report,
@ -41,11 +46,16 @@ class Command(BaseCommand):
context = dict(
user=note.user,
table=table,
last_transactions=last_transactions,
incoming=incoming,
outcoming=outcoming,
diff=incoming - outcoming,
now=now,
last_report=last_report,
)
plain = render_to_string("note/mails/weekly_report.txt", context)
html = render_to_string("note/mails/weekly_report.html", context)
note.user.email_user("[Note Kfet] Rapport de la Note Kfet", html, html_message=html)
if options["debug"]:
self.stdout.write(plain)
else:
note.user.email_user("[Note Kfet] Rapport de la Note Kfet", plain, html_message=html)

View File

@ -1,11 +1,9 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.management.base import BaseCommand
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db import connection
from polymorphic.models import PolymorphicModel
NO_SEQ = [
@ -14,6 +12,7 @@ NO_SEQ = [
"WEIRole", # dirty fix
]
class Command(BaseCommand):
"""
Command to synchronise primary sequence of postgres after bulk insert of django.

3
shell/.env_borg_example Normal file
View File

@ -0,0 +1,3 @@
BORG_PASSPHRASE='CHANGE_ME'
BORG_REPO='USER@SERVER:PATH'
BACKUP_FILE='PATH'

View File

@ -1,11 +1,14 @@
#!/bin/bash
# Create backups directory
[[ -d /var/www/note_kfet/backups ]] || (mkdir /var/www/note_kfet/backups && chown www-data:www-data /var/www/note_kfet/backups)
date=$(date +%Y-%m-%d)
# Backup database and save it as tar archive
su postgres -c "pg_dump -F t note_db" | tee "/var/www/note_kfet/backups/$date.tar" > /dev/null
# Compress backup as gzip
gzip "/var/www/note_kfet/backups/$date.tar"
chown www-data:www-data "/var/www/note_kfet/backups/$date.tar.gz"
# Delete backups that have more than 30 days
find /var/www/note_kfet/backups -type f -mtime +30 -exec rm {} \;
export $(cat .env_borg | xargs)
# Create temporary backups directory
mkdir -p /tmp/note-backups
# Backup database
sudo -u postgres pg_dump -F t note_db > $BACKUP_FILE
# Keep the last 30 backups
borg prune --keep-last 30
# Save backup
borg create --compression lz4 ::backup-{now} $BACKUP_FILE

12
shell/docker_bash Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
if [ -r Dockerfile ]; then
if [ -w /var/run/docker.sock ]; then
docker build -t nk20 .
docker run -it -u $(id -u):$(id -g) --rm -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 bash
else
echo "Merci de rejoindre le groupe docker (ou lancez ce script en sudo) afin de pouvoir vous connecter au socket Docker."
fi
else
echo "N'exécutez ce fichier que dans la racine de votre projet, afin de pouvoir localiser le fichier Dockerfile."
fi

View File

@ -3,7 +3,5 @@ sudo service postgresql stop
sudo service postgresql start
sudo -u postgres sh -c "dropdb note_db && psql -c 'CREATE DATABASE note_db OWNER note;'";
echo 'reset db';
find apps/ -path "*/migrations/*.py*" -not -name "__init__.py" -delete
./manage.py makemigrations
./manage.py migrate
./manage.py loaddata initial

View File

@ -0,0 +1,27 @@
Bonjour {{ user.first_name }} {{ user.last_name }},
Ce message vous est envoyé automatiquement par la Note Kfet du BDE de
l'ENS Cachan, à laquelle vous êtes inscrit·e. Si vous n'êtes plus
adhérent·e, vous n'êtes pas nécessairement concerné·e par la suite
de ce message.
La Note Kfet 2020 vient d'être déployée, succédant à la Note Kfet 2015.
Les données ont été migrées.
Toutefois, la nouvelle note utilise un algorithme de normalisation des alias
permettant de rechercher plus facilement un nom de note, et empêchant la
création d'un alias trop proche d'un autre.
Nous vous informons que les alias suivants ont été supprimés de votre compte,
jugés trop proches d'autres alias déjà existants :
{{ aliases_list|join:", " }}
Nous nous excusons pour le désagrément, et espérons que vous pourrez
profiter de la nouvelle Note Kfet.
Cordialement,
--
Le BDE

View File

@ -0,0 +1,27 @@
Bonjour {{ user.first_name }} {{ user.last_name }},
Ce message vous est envoyé automatiquement par la Note Kfet du BDE de
l'ENS Cachan, à laquelle vous êtes inscrit·e. Si vous n'êtes plus
adhérent·e, vous n'êtes pas nécessairement concerné·e par la suite
de ce message.
La Note Kfet 2020 vient d'être déployée, succédant à la Note Kfet 2015.
Les données ont été migrées.
Toutefois, la nouvelle note utilise un algorithme de normalisation des alias
permettant de rechercher plus facilement un nom de note, et empêchant la
création d'un alias trop proche d'un autre.
Nous vous informons que votre pseudo {{ old_username }} fait pas partie des
alias problématiques. Il a été remplacé par le pseudo {{ new_username }},
que vous devrez utiliser pour pouvoir vous connecter. Il sera ensuite
possible de modifier votre pseudo.
Nous nous excusons pour le désagrément, et espérons que vous pourrez
profiter de la nouvelle Note Kfet.
Cordialement,
--
Le BDE