Compare commits

...

1337 commits

Author SHA1 Message Date
Constantin Gierczak--Galle 8a56e7a280 Remove many things 2024-02-12 10:30:15 +01:00
Tom Hubrecht f640a25f59 Merge branch 'petitcours-template-tweak' into 'master'
[petitcours] Tweak `eleve.txt` template

See merge request klub-dev-ens/gestioCOF!528
2023-10-03 15:20:40 +02:00
Leo Lanteri--Thauvin f881c7cd8b [petitcours] Tweak eleve.txt template 2023-09-11 16:44:45 +02:00
Tom Hubrecht b548b87c25 Version 0.15.1 et Changelog 2023-06-15 13:52:53 +02:00
Tom Hubrecht a72302291f Merge branch 'send_neg' into 'master'
feat(kfet): Change l'adresse utilisée pour envoyer les mails de négatif

See merge request klub-dev-ens/gestioCOF!526
2023-06-15 13:33:44 +02:00
Tom Hubrecht a0bde75f50 feat(kfet): Change l'adresse utilisée pour envoyer les mails de négatif 2023-06-15 13:03:54 +02:00
Tom Hubrecht 44b19c12e5 Merge branch 'send_neg' into 'master'
fix(kfet): Récupère lors d'une erreur due à smtplib

See merge request klub-dev-ens/gestioCOF!525
2023-06-15 10:48:23 +02:00
Tom Hubrecht f97d339a1c fix(kfet): Récupère lors d'une erreur due à smtplib 2023-06-14 20:56:25 +02:00
Tom Hubrecht 094116e88d Merge branch 'thubrecht/date-adhesion' into 'master'
Rajout de la date d'adhésion sur les profils COF

Closes #303

See merge request klub-dev-ens/gestioCOF!521
2023-05-26 09:31:45 +02:00
Tom Hubrecht b32a07fc22 Version 0.15 et mise à jour du Changelog 2023-05-22 20:42:23 +02:00
Tom Hubrecht 4fc9902cf6 Merge branch 'thubrecht/contact-soiree' into 'master'
feat(kfet): Ajout d'un formulaire de demande de soirée

See merge request klub-dev-ens/gestioCOF!523
2023-05-22 20:37:38 +02:00
Tom Hubrecht 7164cfa37a feat(kfet): Ajout d'un formulaire de demande de soirée 2023-05-22 20:30:05 +02:00
Tom Hubrecht 90f96fb5c9 Merge branch 'thubrecht/contact' into 'master'
feat(kfet): Ajoute un formulaire de contact

Closes #302

See merge request klub-dev-ens/gestioCOF!520
2023-05-22 19:06:26 +02:00
Tom Hubrecht e50249355d feat(kfet): Ajoute un formulaire de contact 2023-05-22 18:59:46 +02:00
Tom Hubrecht c304d734d9 Merge branch 'thubrecht/comptes-inactifs' into 'master'
feat(kfet): Désactive l'envoi des mails pour les comptes gelés

See merge request klub-dev-ens/gestioCOF!522
2023-05-22 18:34:51 +02:00
Tom Hubrecht c36dd30bce fix(kfet): Affiche la bonne information 2023-05-22 18:26:24 +02:00
Tom Hubrecht 2571cc955e feat(kfet): Désactive l'envoi des mails pour les comptes gelés
On utilise la fonctionnalité `is_frozen` pour marquer les comptes qui n'ont plus d'adresse valide, et on répare le formulaire de màj de compte.
2023-05-22 18:23:50 +02:00
Tom Hubrecht 3eaac5c68f feat(cof): Rajoute la date d'adhésion dans les profils 2023-05-22 11:28:23 +02:00
Tom Hubrecht af4c8e0744 Update shell.nix and use django-types 2023-05-22 10:57:11 +02:00
Tom Hubrecht 14e0a3ef0a Version 0.14 et mise à jour du changelog 2023-05-19 20:18:11 +02:00
Tom Hubrecht 83078d4726 Merge branch 'thubrecht/date-js' into 'master'
Thubrecht/date js

See merge request klub-dev-ens/gestioCOF!518
2023-05-19 17:34:27 +02:00
Tom Hubrecht cb262ad479 fix(kfet): Update timezone data for moment.js 2023-05-19 16:45:15 +02:00
Tom Hubrecht 5c47118834 Update gitignore and shell.nix 2023-05-19 15:14:55 +02:00
Tom Hubrecht 30e842ce80 shell.nix: Update to use virtualenv 2023-05-19 14:59:19 +02:00
Tom Hubrecht 892bf51163 Run black on all files 2023-05-19 14:57:48 +02:00
Tom Hubrecht e20d7ca6c2 requirements: Fix required versions 2023-05-19 14:43:25 +02:00
Tom Hubrecht 1b09293206 Version 0.13 && Update changelog 2023-02-19 10:32:47 +01:00
Tom Hubrecht e7da476697 Merge branch 'thubrecht/surnom' into 'master'
Rend le surnom lisible par la personne

Closes #297

See merge request klub-dev-ens/gestioCOF!506
2023-01-31 17:06:28 +01:00
Tom Hubrecht 761ab6df90 Merge branch '_aandres/inventory_sum_amounts' into 'master'
Affiche les montants des valeurs des stocks sur l'affichage d'un inventaire

See merge request klub-dev-ens/gestioCOF!515
2023-01-28 15:34:56 +01:00
Tom Hubrecht a8d4035d33 Merge branch 'thubrecht/rappels_negatifs' into 'master'
On n'envoie des mails de rappel que lorsque le négatif est toujours d'actualité

Closes #298

See merge request klub-dev-ens/gestioCOF!509
2023-01-28 15:33:23 +01:00
Alice b80426c56f feat(kfet): message info about prices 2023-01-28 15:28:54 +01:00
Alice 429e611daa feat(kfet): more values and formatting 2023-01-28 15:28:54 +01:00
Tom Hubrecht 5160da7862 Merge branch '292_fix_cancel_js' into 'master'
fix(kfet): fix js error when cancelling already canceled operation

Closes #292

See merge request klub-dev-ens/gestioCOF!516
2023-01-28 15:20:53 +01:00
Alice aad3775222 fix(kfet): fix js error when cancelling already canceled operation 2023-01-28 15:13:55 +01:00
Alice 4b92716092 feat: poc inventory amount value
and lint
2023-01-23 21:53:46 +01:00
Tom Hubrecht dfa5b4bf69 changelog: add current date 2022-10-03 18:37:41 +02:00
Tom Hubrecht 1be5dcb6af Merge branch 'thubrecht/kfetcms-css' into 'master'
kfetcms: Update the fixtures and fix the navbar behaviour due to a longer menu

See merge request klub-dev-ens/gestioCOF!513
2022-10-03 10:54:37 +02:00
Tom Hubrecht a891ec56a6 dev: add nixos setup 2022-10-03 10:34:06 +02:00
Tom Hubrecht 7a52690a63 kfet: fix pipeline 2022-10-03 10:33:29 +02:00
Tom Hubrecht a2f396ce7a Changelog: Update 2022-10-03 10:33:04 +02:00
Tom Hubrecht 85e30056a6 kfetcms: Update the fixtures and fix the navbar behaviour due to a longer menu 2022-10-03 10:22:32 +02:00
Tom Hubrecht 69de48f285 Version 0.12 2022-06-17 21:45:34 +02:00
Tom Hubrecht eba36f2712 Merge branch 'dodo/kfet-history-limit-exceptions' into 'master'
Dodo/kfet history limit exceptions

See merge request klub-dev-ens/gestioCOF!508
2022-05-20 12:08:59 +02:00
Dorian Lesbre bfdb34aae7 Dodo/kfet history limit exceptions 2022-05-20 12:08:59 +02:00
Tom Hubrecht fcf2002cd7 On n'affiche le négatif que s'il existe vraiment 2022-01-11 18:10:00 +01:00
Tom Hubrecht b236d6a950 Si last_rappel vaut None il n'est pas inclus dans le __lt 2022-01-11 18:10:00 +01:00
Tom Hubrecht 4b29097f02 On sauvegarde la date de fin du négatif 2022-01-11 18:10:00 +01:00
Tom Hubrecht 87f383bef1 On n'envoie des mails de rappel que lorsque le négatif est toujours d'actualité 2022-01-11 18:10:00 +01:00
Tom Hubrecht 1ad025e046 Merge branch 'dodo/fix_stat_labels' into 'master'
Dodo/fix stat labels

Closes #296

See merge request klub-dev-ens/gestioCOF!510
2022-01-07 11:07:51 +01:00
Dorian Lesbre b3c047738a Ajout accent K-Fêt 2022-01-06 16:00:26 +01:00
Dorian Lesbre 17a9ae3302 Update CHANGELOG 2022-01-05 10:50:45 +01:00
Dorian Lesbre e41bcbb6d7 Removed duplicate import to please flake8 2022-01-05 10:48:04 +01:00
Dorian Lesbre e384bfb0f3 Fix issue #296 2022-01-05 10:45:32 +01:00
Tom Hubrecht 373ff1f62c Rend le surnom lisible par la personne 2021-11-25 14:44:40 +01:00
Martin Pepin 65eb95a3c9 Merge branch 'thubrecht/bds-membres' into 'master'
Réinitialisation des adhésions

Closes #294

See merge request klub-dev-ens/gestioCOF!503
2021-10-27 14:33:18 +02:00
Martin Pépin 1c880b265e
Version 1.11 2021-10-26 19:49:05 +02:00
Martin Pépin 75fbdc7efb
CHANGELOG: todo prod: faire un compte hcaptcha 2021-10-26 19:47:38 +02:00
Tom Hubrecht 1b8dd971b0 Ajoute un mécanisme de réinitialisation des adhésions 2021-10-26 10:26:22 +02:00
Tom Hubrecht 713d686047 Corrige l'affichage de la date dans le formulaire 2021-10-26 09:24:44 +02:00
Martin Pepin 77aa269c90 Merge branch 'Mails_rappel_kfet' into 'master'
Reminder mails for negative K-Psul accounts

See merge request klub-dev-ens/gestioCOF!492
2021-10-22 22:44:55 +02:00
Martin Pépin 9a143521d5
Update changelog 2021-10-22 22:37:56 +02:00
Martin Pépin a77cf59b18
Rappels négatifs K-Fêt: ajustements cosmétiques 2021-10-22 22:36:30 +02:00
Alseidon d8cabda678
First draft of reminder mail for negative K-Psul accounts 2021-10-22 21:29:34 +02:00
Martin Pépin f086140dad
Update changelog 2021-10-22 21:12:16 +02:00
Martin Pepin 4d1ae8f540 Merge branch 'thubrecht/embed' into 'master'
On utilise |richtext pour les champs RichText, ce qui permet de bien faire les rendus

Closes #274

See merge request klub-dev-ens/gestioCOF!500
2021-10-22 21:04:20 +02:00
Ludovic Stephan 6d824a58be Merge branch 'thubrecht/ordre-consos' into 'master'
Corrige le tri des articles dans K-Psul

See merge request klub-dev-ens/gestioCOF!502
2021-10-12 16:05:26 +02:00
Tom Hubrecht 20880114aa Corrige le tri des articles dans K-Psul 2021-10-12 15:57:09 +02:00
Ludovic Stephan df180d7446 Merge branch 'thubrecht/promo' into 'master'
Déplace le choix de la promo dans le formulaire

Closes #215

See merge request klub-dev-ens/gestioCOF!501
2021-07-01 09:03:47 +00:00
Tom Hubrecht ef1793a348 Avec une seule majuscule 2021-07-01 10:29:54 +02:00
Tom Hubrecht 2d677b2093 Utilise des callables pour les choix 2021-07-01 10:29:14 +02:00
Tom Hubrecht f70eacfc37 Déplace le choix de la promo dans le formulaire 2021-06-27 00:23:49 +02:00
Tom Hubrecht 264a0a852f On utilise |richtext pour les champs RichText, ce qui permet de bien faire les rendus 2021-06-26 22:52:23 +02:00
Tom Hubrecht 7ca7f7298a Update CHANGELOG 2021-06-17 21:28:08 +02:00
Tom Hubrecht a5c822e7f7 Merge branch 'Aufinal/remove_negative' into 'master'
Fonctionnement du négatif + erreurs de K-Psul

Closes #279

See merge request klub-dev-ens/gestioCOF!494
2021-06-17 19:22:14 +00:00
Ludovic Stephan 6b316c482b Remove obsolete section 2021-06-17 17:22:17 +02:00
Ludovic Stephan 4060730ec5 Remove logging 2021-06-17 10:49:35 +02:00
Ludovic Stephan c6cfc311e0 CHANGELOG 2021-06-17 10:45:53 +02:00
Ludovic Stephan 4326ba9016 Oublis de renaming 2021-06-17 10:42:15 +02:00
Ludovic Stephan 4205e0ad0e Tests 2021-06-17 10:42:15 +02:00
Ludovic Stephan 964eec6ab1 Adapte le JS aux nouvelles erreurs 2021-06-17 10:42:13 +02:00
Ludovic Stephan 29236e0b0e Nouvelle gestion des erreurs JSON 2021-06-17 10:40:51 +02:00
Ludovic Stephan 1939a54fef Tests du nouveau comportement 2021-06-17 10:40:51 +02:00
Ludovic Stephan 348881d207 Migration 2021-06-17 10:40:51 +02:00
Ludovic Stephan ef8c1b8bf2 Nouveau fonctionnement des négatifs 2021-06-17 10:40:51 +02:00
Tom Hubrecht 8743301105 Merge branch 'Aufinal/let_it_go' into 'master'
Change le fonctionnement du gel de compte

Closes #280

See merge request klub-dev-ens/gestioCOF!493
2021-06-15 15:24:44 +00:00
Ludovic Stephan 6a11139588 Fix tests 2021-06-15 16:52:56 +02:00
Ludovic Stephan a34b83c236 Use backend to enforce frozen accounts 2021-06-15 16:52:50 +02:00
Ludovic Stephan 02584982f6 gnagnagna 2021-06-15 14:48:35 +02:00
Ludovic Stephan 7bf0c5f09e Fix frozen forms 2021-06-15 14:07:43 +02:00
Ludovic Stephan b9aaf6a19c Fix test 2021-06-15 14:07:43 +02:00
Ludovic Stephan 16dee0c143 Remove print 2021-06-15 14:07:43 +02:00
Ludovic Stephan a947b9d3f2 Fix decorator 2021-06-15 14:07:43 +02:00
Ludovic Stephan 93d283fecb Remove unused permission 2021-06-15 14:07:43 +02:00
Ludovic Stephan 63738e8e02 Frozen error display 2021-06-15 14:07:40 +02:00
Ludovic Stephan 1e44550e12 New frozen function 2021-06-15 14:05:39 +02:00
Ludovic Stephan 4136cb6868 Unfreeze every account 2021-06-15 14:05:39 +02:00
Ludovic Stephan 99809209e0 Change les permissions pour geler/dégeler un compte 2021-06-15 14:05:39 +02:00
Ludovic Stephan 0351f6728b CHANGELOG 2021-05-05 02:10:44 +02:00
Ludovic Stephan 7efc7e6b94 Merge branch 'thubrecht/autocomplete-css' into 'master'
On modifie le curseur quand on survole un compte dans l'autocomplete

See merge request klub-dev-ens/gestioCOF!499
2021-05-05 00:06:15 +00:00
Tom Hubrecht 7d21a5a1fc On supprime des sélecteurs inutiles 2021-05-05 01:57:46 +02:00
Tom Hubrecht dba785bf13 Pareil, mais dans gestiocof 2021-05-05 00:59:47 +02:00
Tom Hubrecht 71878caf2c On modifie le curseur quand on survole un compte dans l'autocomplete 2021-05-05 00:03:52 +02:00
Tom Hubrecht db42028228 Merge branch 'Aufinal/backbone' into 'master'
Refactor le JS de K-Psul via Backbone : 2e étape

Closes #267 and #290

See merge request klub-dev-ens/gestioCOF!400
2021-05-04 21:17:09 +00:00
Ludovic Stephan 7171a7567c Remove double negative 2021-05-04 21:43:48 +02:00
Ludovic Stephan 339223bec0 Black 2021-05-04 18:12:47 +02:00
Ludovic Stephan d62a8d61de Search fix and CSS update 2021-05-04 17:52:13 +02:00
Ludovic Stephan f6c83dc692 FINALLY fix this f***ing whitespace mess 2021-05-04 17:52:13 +02:00
Ludovic Stephan a984d1fd6f Clarity 2021-05-04 17:52:13 +02:00
Ludovic Stephan f901ea9396 Remove useless kpsul.html code 2021-05-04 17:52:13 +02:00
Ludovic Stephan 17d96f1775 New account manager logic 2021-05-04 17:52:13 +02:00
Ludovic Stephan c10e5fe45c Refactor Account model a bit 2021-05-04 17:52:13 +02:00
Martin Pépin 9bbe3f50cb
Update CHANGELOG.md 2021-04-18 18:17:38 +02:00
Martin Pépin 1f4a4ec76f
Update CHANGELOG.md 2021-04-18 17:46:54 +02:00
Martin Pepin 2befa584aa Merge branch 'Aufinal/remove_24' into 'master'
Remove limit for purchases

Closes #289

See merge request klub-dev-ens/gestioCOF!498
2021-04-18 08:35:24 +00:00
Ludovic Stephan b48d32f4bc Remove limit for purchases 2021-04-16 16:42:12 +02:00
Tom Hubrecht e36e88e77a Merge branch 'Aufinal/no_warnings' into 'master'
Fix : plus de warnings chelous pendant les tests

See merge request klub-dev-ens/gestioCOF!495
2021-03-17 22:12:01 +00:00
Tom Hubrecht 8e9fc341ca Merge branch 'Aufinal/forbidden_kfet' into 'master'
Test plus général pour l'erreur de permissions K-Fêt

See merge request klub-dev-ens/gestioCOF!491
2021-03-16 23:22:18 +01:00
Ludovic Stephan c14c2d54a5 More general forbidden test 2021-03-16 23:04:03 +01:00
Tom Hubrecht 6adfaba8e9 Merge branch 'Aufinal/account_update_forms' into 'master'
Refactor la vue `account_update`

Closes #232 and #119

See merge request klub-dev-ens/gestioCOF!490
2021-03-16 23:02:12 +01:00
Tom Hubrecht 06005014f9 Merge branch 'Aufinal/delete_balance_offset' into 'master'
Supprime le champ `balance_offset` et harmonise la gestion des négatifs

Closes #281

See merge request klub-dev-ens/gestioCOF!489
2021-03-16 22:42:13 +01:00
Ludovic Stephan 4268a30d51 CHANGELOG 2021-03-16 22:10:33 +01:00
Tom Hubrecht c71e6d22bf Merge branch 'Aufinal/hcaptcha' into 'master'
Remplace recaptcha par hcaptcha

Closes #262

See merge request klub-dev-ens/gestioCOF!497
2021-03-16 22:00:48 +01:00
Ludovic Stephan 4df3ef4dd9 Fix secret import 2021-03-04 23:28:55 +01:00
Ludovic Stephan af95e64344 TODO de prod 2021-03-04 23:14:10 +01:00
Ludovic Stephan ac8ad15ad1 Fix tests: mock captcha clean method 2021-03-04 18:30:51 +01:00
Ludovic Stephan 47dd078b6a Remplace recaptcha par hcaptcha 2021-03-04 17:56:42 +01:00
Ludovic Stephan 472a44c30f Remove useless buttons 2021-03-03 23:11:39 +01:00
Ludovic Stephan b72ea9ebf9 Forgot a warning 2021-02-28 02:56:12 +01:00
Ludovic Stephan f9958e4da0 Fix : plus de warnings chelous pendant les tests 2021-02-28 02:35:40 +01:00
Ludovic Stephan 47f406e09e Fix tests 2021-02-23 22:52:39 +01:00
Ludovic Stephan 1450b65dcd Rework complet de account_update 2021-02-23 22:52:39 +01:00
Ludovic Stephan aac94afcd0 Améliore le formulaire de mdp K-Fêt 2021-02-23 22:52:39 +01:00
Ludovic Stephan 209360f535 Delete self-update form 2021-02-23 22:52:39 +01:00
Ludovic Stephan b224fedf28 Fix frozen account display 2021-02-23 22:52:39 +01:00
Ludovic Stephan 1ab071d16e LINT 2021-02-23 22:52:27 +01:00
Ludovic Stephan 1cf6f6f3e7 Fix migration conflict 2021-02-23 22:41:04 +01:00
Ludovic Stephan a421bec625 Fix templates 2021-02-23 22:33:00 +01:00
Ludovic Stephan 4e758fbba0 Delete balance_offset field 2021-02-23 22:33:00 +01:00
Martin Pepin 2350109a33 Merge branch 'Aufinal/migration_checks' into 'master'
CI: ne lance `migration_checks` que sur nos propres apps

See merge request klub-dev-ens/gestioCOF!488
2021-02-23 21:18:36 +01:00
Ludovic Stephan 778637d60e Merge branch 'dodo/limit-history-acces' into 'master'
Limit kfet history access

See merge request klub-dev-ens/gestioCOF!487
2021-02-20 22:58:59 +01:00
Dorian Lesbre 23f7865140 Switch back from config to settings 2021-02-20 20:59:54 +01:00
Dorian Lesbre cc7c4306f4 Added change description to CHANGELOG 2021-02-20 19:10:49 +01:00
Dorian Lesbre 1183e50f60 Fixed tests 2021-02-19 13:48:12 +01:00
Dorian Lesbre a8de7e0ae0 makemigrations 2021-02-19 13:38:36 +01:00
Dorian Lesbre 30a39ef2f6 Switch from account test to user test 2021-02-19 12:16:43 +01:00
Dorian Lesbre 9a635148bb Switched from datetime.today() to timezone.now() 2021-02-19 12:13:23 +01:00
Dorian Lesbre 4b95b65be2 Removed unused import 2021-02-19 11:55:18 +01:00
Dorian Lesbre 884ec2535b Fixed stupid errors 2021-02-19 11:51:48 +01:00
Dorian Lesbre beba3052dd Switched from hardcoded settings to config 2021-02-19 11:46:42 +01:00
Dorian Lesbre 46242ad2c0 Added separate permission for chef/trez 2021-02-19 10:48:24 +01:00
Dorian Lesbre fa8c57269c Added help_text to history form 2021-02-19 10:32:12 +01:00
Dorian Lesbre b97bc8bfa8 Changed accoutn comparaison from id to equality 2021-02-19 10:26:05 +01:00
Dorian Lesbre 89fc309c01 Returned 403 on dubious history request 2021-02-19 10:18:47 +01:00
Ludovic Stephan d7367476bc Fix app names 2021-02-18 17:41:52 +01:00
Ludovic Stephan 7297baaf7e Only check migrations for custom apps 2021-02-18 17:30:28 +01:00
Ludovic Stephan 8bf7914728 Merge branch 'kerl/fix_bds_production_urls' into 'master'
Hotfixes appliqués en production pour GestioBDS

See merge request klub-dev-ens/gestioCOF!484
2021-02-18 17:02:21 +01:00
Ludovic Stephan 71fbbcff8a Merge branch 'kerl/rm_login_clipper' into 'master'
Admin : supprime la colonne login_clipper dans la liste des Users

See merge request klub-dev-ens/gestioCOF!486
2021-02-18 17:01:05 +01:00
Dorian Lesbre 9303772f9a Renamed week_ago => history_limit and removed print 2021-02-10 22:19:52 +01:00
Tom Hubrecht 559b36b6f0 Limite le datepicker pour ne pas demander plus de temps que possible dans l'historique 2021-02-10 22:13:50 +01:00
Dorian Lesbre fbafdb7134 Added kfet history date limit when not accessing own account 2021-02-10 21:32:44 +01:00
Martin Pépin a53bd94737
admin: rm the login_clipper column in the user list 2021-02-09 22:42:49 +01:00
Ludovic Stephan 46ef12309a Merge branch 'kerl/rename_cof_gestioAsso' into 'master'
Renomme le dossier cof/ en gestioasso/

See merge request klub-dev-ens/gestioCOF!485
2021-02-08 19:29:07 +01:00
Martin Pépin 4f60ba35eb
Update the settings' docstrings 2021-02-08 19:19:54 +01:00
Martin Pépin f29b3f0187
Make "GestioBDS" appear in the README 2021-02-07 18:11:17 +01:00
Martin Pépin aa3462aaee
Update the CI config wrt the new project name 2021-02-07 18:11:06 +01:00
Martin Pépin 7c35357060
Fix a reverse url resolution on the BDS home page 2021-02-07 17:39:28 +01:00
Martin Pépin 726b3f55a0
Rename the cof/ folder to gestioasso/
This is a much more sensible name since it contains configuration
applicable to both GestioCOF and GestioBDS.

The next logical step would be to rename the `gestioncof/` folder to
`cof/`.
2021-02-07 17:17:15 +01:00
Martin Pepin 63eeb5b7a9 Merge branch 'Aufinal/single_checkout' into 'master'
Fix "Checkout is not iterable" error

See merge request klub-dev-ens/gestioCOF!483
2021-02-07 16:40:48 +01:00
Martin Pépin 7081380058
Only redirect / → /gestion in development 2021-02-07 16:29:47 +01:00
Ludovic Stephan 288de95c49 Checkout form is single-option now 2021-02-06 18:58:25 +01:00
Martin Pépin 9a01d1e877
CHANGELOG: add missing items in the v0.9 release 2021-02-06 17:17:47 +01:00
Martin Pépin 10746c0469
Version 0.9 2021-02-06 17:01:22 +01:00
Basile Clement 5c8eca15b6 Merge branch 'kerl/admin_autocomplete' into 'master'
Admin: on utilise la recherche builtin de Django

See merge request klub-dev-ens/gestioCOF!476
2021-02-04 22:16:27 +01:00
Martin Pepin ef64f9ce5c Merge branch 'Buro_rights_by_BDS' into 'master'
Added basic buro right handling while updating member

See merge request klub-dev-ens/gestioCOF!482
2021-01-30 15:06:52 +01:00
Alseidon 9762838921
Basic Buro right handling - minor corrections 2021-01-30 14:59:04 +01:00
Alseidon bf6d6d6430
Added basic buro right handling while updating member 2021-01-30 14:57:24 +01:00
Martin Pepin ba9aa06b4f Merge branch 'dodo/bds_export_csv' into 'master'
Dodo/bds export csv

Closes #285

See merge request klub-dev-ens/gestioCOF!481
2021-01-29 19:38:55 +01:00
Dorian Lesbre 880dc31353 Update CHANGELOG.md 2021-01-29 09:37:37 +01:00
Dorian Lesbre 9a78fca507 Switched to named url 2021-01-29 09:34:56 +01:00
Ludovic Stephan 4bc56d34e0 Fix tests 2021-01-21 21:08:57 +01:00
Ludovic Stephan 79f0757e9f Fix kfet stats 2021-01-21 20:55:23 +01:00
Dorian Lesbre a2eed13717 Added download button to home template 2021-01-21 20:38:15 +01:00
Dorian Lesbre 830aba984e Added bds/members to export members list as CSV 2021-01-21 20:32:36 +01:00
Ludovic Stephan 33319cfe76 Merge branch 'newbie' into 'master'
Reset comptes COF

See merge request klub-dev-ens/gestioCOF!479
2021-01-07 09:32:33 +01:00
Alseidon 44b001bd3c Satisfy Lord Black 2021-01-07 09:19:56 +01:00
Martin Pepin ab9d95055e Merge branch 'Tragicus/kfetTriArticles' into 'master'
kfet articles vendus en premier dans inventaire et commandes

Closes #219

See merge request klub-dev-ens/gestioCOF!477
2021-01-06 21:41:39 +01:00
Martin Pépin 40391d8814
Update CHANGELOG.md 2021-01-06 21:32:41 +01:00
Martin Pépin 681507f211
Happy new year! 2021-01-06 21:31:47 +01:00
Ludovic Stephan 7f133316a4 Merge branch 'Aufinal/black_required' into 'master'
Add black to requirements-devel

See merge request klub-dev-ens/gestioCOF!480
2020-12-17 18:58:59 +01:00
Ludovic Stephan 0bdbcf59fa Add black to requirements-devel 2020-12-10 16:46:53 +01:00
Ludovic Stephan 0bad404b71 Merge branch 'Dodo/date_fermeture_bda' into 'master'
Ajout date de fermeture de tirage BDA sur la page d'acceuil

Closes #172

See merge request klub-dev-ens/gestioCOF!478
2020-12-10 14:20:56 +01:00
Alseidon c100f2fc8d Version 1.1 remise à zéro comptes COF 2020-12-09 23:00:00 +01:00
Alseidon ba74779f95 Version 1.0 remise à zéro comptes COF 2020-12-09 22:40:32 +01:00
Quentin VERMANDE 035bbe68a5 make black happy 2020-12-09 22:22:12 +01:00
Alseidon 319db68655 Ra0 effective 2020-12-09 22:11:21 +01:00
Quentin VERMANDE 9d2c13e67c kfetTriArticles 2020-12-09 22:03:54 +01:00
Alseidon 73c068055b Remise à zéro basique comptes COF 2020-12-09 21:57:40 +01:00
Dorian Lesbre 30ce8d13af Ajout date de fermeture de tirage BDA sur la page d'acceuil 2020-12-09 21:16:49 +01:00
Ludovic Stephan 340f8f16a7 Merge branch 'kerl/fix_vagrant' into 'master'
Mise à jour du setup vagrant

See merge request klub-dev-ens/gestioCOF!475
2020-12-07 20:56:09 +01:00
Martin Pépin 0ce1e62586
Fichier bootstrap.sh mieux commenté 2020-12-07 20:09:25 +01:00
Martin Pépin 783fe1de32
Liste des paquets dans un fichier séparé 2020-12-07 20:09:24 +01:00
Martin Pépin 49fde85187
Admin: on utilise la recherche builtin de Django 2020-12-04 19:33:17 +01:00
Martin Pepin 479e751b7c Merge branch 'Aufinal/can_resell_paid' into 'master'
On peut revendre une place dès qu'on l'a payée

Closes #277

See merge request klub-dev-ens/gestioCOF!473
2020-12-04 18:01:22 +01:00
Martin Pépin f2c1ff2abd
Update CHANGELOG 2020-12-04 17:53:56 +01:00
Ludovic Stephan 8b73460165
Make flake8 happy 2020-12-04 17:52:33 +01:00
Ludovic Stephan 411d7e7dce
On peut revendre une place qu'on a payée 2020-12-04 17:52:21 +01:00
Martin Pepin f952d50b12 Merge branch 'Aufinal/email_validation' into 'master'
Utilise un EmailField pour valider des emails

See merge request klub-dev-ens/gestioCOF!471
2020-12-04 17:29:07 +01:00
Ludovic Stephan 7324a72e6e Merge branch 'kerl/make_kfetloaddevdata_idempotent' into 'master'
Rend kfetloaddevdata idempotent

See merge request klub-dev-ens/gestioCOF!474
2020-12-04 17:17:38 +01:00
Martin Pépin ad73cc987d
CHANGELOG 2020-12-04 17:16:35 +01:00
Ludovic Stephan e9e0c79b40
Migration 2020-12-04 17:15:15 +01:00
Ludovic Stephan badee498a3
Use EmailField for email field 2020-12-04 17:15:15 +01:00
Martin Pepin 72cd55716b Merge branch 'Aufinal/inventory_delete' into 'master'
Permet l'annulation d'un inventaire

Closes #251

See merge request klub-dev-ens/gestioCOF!457
2020-12-04 17:13:32 +01:00
Martin Pépin a7cbd2d451
CHANGELOG 2020-12-04 17:02:36 +01:00
Ludovic Stephan b9699637aa
Message de confirmation plus clair 2020-12-04 17:01:25 +01:00
Ludovic Stephan 521be6db85
Tests 2020-12-04 17:01:25 +01:00
Ludovic Stephan f3701d91fc
Url and template for InventoryDeleteView 2020-12-04 17:01:25 +01:00
Ludovic Stephan 59dacda37d
Inventory deletion view 2020-12-04 17:01:25 +01:00
Martin Pépin 7f58b5fa00
Vagrant: toutes les units systemd sont là 2020-12-04 16:58:25 +01:00
Martin Pépin df222f18a3
Update the vagrant config → should work now 2020-12-04 16:10:27 +01:00
Martin Pépin 5d22a4cac4
Rend kfetloaddevdata idempotent
Problème :

Le script assigne des trigrammes 001, 002, 003, etc aux comptes COF des
Gaulois et des Romains en utilisant l'ordre du queryset
CofProfile.objects.all().
L'ordre des comptes dans le queryset n'est pas spécifié et peut varier
d'une exécution à l'autre, ça pose problème dans la suite :

Account.objects.get_or_create(trigramme=trigramme, cofprofile=profile)

Cette command essaie de créer un nouveau trigramme pour certains comptes
quand l'ordre change.

Solution :

Ordonner le queryset.
2020-12-04 12:44:09 +01:00
Martin Pépin cc3a436750
Version 0.8 2020-12-03 20:22:31 +01:00
Ludovic Stephan 404d3f4f4c Merge branch 'Aufinal/spectacle_paid' into 'master'
Fix `paid` field in `bda/spectacles`

See merge request klub-dev-ens/gestioCOF!472
2020-10-29 11:15:41 +01:00
Ludovic Stephan 43fcdc8526 Fix paid field in bda/spectacles 2020-10-28 14:35:45 +01:00
Ludovic Stephan 35f896b40f CHANGELOG 2020-10-23 10:25:23 +02:00
Ludovic Stephan fb1a38cff3 Merge branch 'Aufinal/bda_admin_misc' into 'master'
Ergonomie de l'admin du BdA

Closes #276

See merge request klub-dev-ens/gestioCOF!469
2020-10-23 10:11:15 +02:00
Ludovic Stephan f88795a60e Use same qset for every field 2020-10-22 19:34:59 +02:00
Ludovic Stephan 0ffebdf82f Merge branch 'Aufinal/meta' into 'master'
Fix : autocomplétion sans classe `Meta`

See merge request klub-dev-ens/gestioCOF!468
2020-10-22 18:54:17 +02:00
Ludovic Stephan 147b8514ef Limite les select au tirage concerné 2020-10-21 18:22:48 +02:00
Ludovic Stephan d535cf24a3 Migration 2020-10-21 18:22:19 +02:00
Ludovic Stephan 84dab59c72 Ordre des participants + unicité 2020-10-21 18:22:05 +02:00
Ludovic Stephan 22cf0d403e Permet d'archiver un tirage 2020-10-21 18:21:40 +02:00
Ludovic Stephan a525cffaff Fix participant autocomplete 2020-10-21 16:02:01 +02:00
Martin Pepin 1af602c9f7 Merge branch 'Aufinal/listing' into 'master'
Indique si les places sont sur listing

Closes #213

See merge request klub-dev-ens/gestioCOF!467
2020-09-22 21:48:40 +02:00
Martin Pépin 8a17aa2caa
Update CHANGELOG 2020-09-22 21:35:50 +02:00
Ludovic Stephan 7a9d96d83a
Indique si les places sont sur listing 2020-09-22 21:34:53 +02:00
Martin Pépin 3869c02dfa
Merge branch 'Aufinal/fix-fa-again' into master 2020-09-22 21:23:49 +02:00
Martin Pépin 848eb2274a
Update CHANGELOG 2020-09-22 21:23:20 +02:00
Ludovic Stephan ebd8b7ccdb
Fix fa path in petitscours 2020-09-22 21:22:16 +02:00
Martin Pépin 3c6ab35390
Update CHANGELOG 2020-09-22 21:20:14 +02:00
Martin Pepin 57901c0013 Merge branch 'Aufinal/stat_2' into 'master'
Repassage sur les stats

Closes #246 and #255

See merge request klub-dev-ens/gestioCOF!462
2020-09-22 21:06:46 +02:00
Ludovic Stephan d172dad0ab Merge branch 'thubrecht/creation-compte' into 'master'
Empêche la modification des informations COF lors de la création d'un compte K-Psul

Closes #230

See merge request klub-dev-ens/gestioCOF!464
2020-09-21 16:43:17 +02:00
Tom Hubrecht d0b7000747 Empêche la modification des informations COF lors de la création d'un compte K-Psul 2020-09-19 19:14:44 +02:00
Martin Pepin 84ff0d7182 Merge branch 'Aufinal/history_form' into 'master'
On utilise un vrai formulaire pour l'historique

Closes #242

See merge request klub-dev-ens/gestioCOF!461
2020-09-17 21:04:42 +02:00
Martin Pepin 600927b21c Merge branch 'Aufinal/rip-custommail' into 'master'
Supprime `custommail` de gestioCOF

Closes #227

See merge request klub-dev-ens/gestioCOF!459
2020-09-17 20:22:11 +02:00
Ludovic Stephan d965050563 Fix tests again 2020-09-16 19:31:10 +02:00
Ludovic Stephan a14c9d9574 Fix tests 2020-09-16 19:19:29 +02:00
Ludovic Stephan 8f9c94fe10 Plein de nettoyage partout 2020-09-16 17:16:49 +02:00
Ludovic Stephan 46f447ec5d Formulaires pour nettoyage 2020-09-16 17:16:14 +02:00
Ludovic Stephan 4dbf11f91e Template tweaks 2020-09-15 21:10:36 +02:00
Martin Pepin 569ce0ba25 Merge branch 'Aufinal/open_password' into 'master'
Fix : fermeture manuelle de la K-Fêt avec mot de passe

Closes #183

See merge request klub-dev-ens/gestioCOF!460
2020-09-15 20:22:05 +02:00
Ludovic Stephan aa955a06ef Fin des adaptations 2020-09-15 20:05:54 +02:00
Ludovic Stephan a9eb32217f Adapte history.js pour serialize() 2020-09-15 20:05:32 +02:00
Ludovic Stephan c7998f56f0 Datetimepicker tweaks 2020-09-15 20:05:06 +02:00
Ludovic Stephan a6e58dcd68 On utilise le render par défaut 2020-09-15 20:04:35 +02:00
Ludovic Stephan 49591fa67e Use form to clean data 2020-09-15 20:03:37 +02:00
Ludovic Stephan 9f9724b1d1 Arrow function works now 2020-09-15 19:57:27 +02:00
Ludovic Stephan 205dc93f4b FilterHistoryForm est un formulaire décent 2020-09-15 19:40:45 +02:00
Martin Pepin 7f6d4527ed Merge branch 'Aufinal/petitscours_uniqueness' into 'master'
Fix : KeyError sur les petits cours

See merge request klub-dev-ens/gestioCOF!458
2020-09-15 19:38:15 +02:00
Ludovic Stephan 43a2f8db53 Use arrow functions everywhere for consistency 2020-09-15 16:44:32 +02:00
Ludovic Stephan ba4cc01ed4 Fix formatting 2020-09-15 16:37:41 +02:00
Ludovic Stephan 11d94ecba8 Fix this shenanigans 2020-09-15 16:34:19 +02:00
Ludovic Stephan 82d58d23c9 Remove all traces of custommail 2020-09-15 11:49:32 +02:00
Ludovic Stephan 6377dd5c95 BdA : tests 2020-09-15 11:49:18 +02:00
Ludovic Stephan f364928004 Remove custommail in bda 2020-09-15 11:49:05 +02:00
Ludovic Stephan dc070278f7 Gestioncof : tests 2020-09-15 11:48:36 +02:00
Ludovic Stephan 561a121e04 Remove custommail in gestioncof 2020-09-15 11:48:21 +02:00
Ludovic Stephan b03cf05ef7 Petits cours : tests 2020-09-15 11:47:53 +02:00
Ludovic Stephan edf6a03bc4 Phase out custommail in petitscours 2020-09-15 11:47:28 +02:00
Ludovic Stephan eb3cba31a7 Emails as text files 2020-09-15 11:15:12 +02:00
Ludovic Stephan 1ffda1a5c4 Better uniqueness checks 2020-09-14 11:40:21 +02:00
Martin Pépin 2bc97a115c
Version 0.7.2 2020-09-08 20:06:26 +02:00
Ludovic Stephan b8072f4346 Merge branch 'kerl/404' into 'master'
Nouvelle page 404

See merge request klub-dev-ens/gestioCOF!454
2020-09-07 20:33:43 +02:00
Martin Pépin cedd3cf816
404: english text looks better in italic 2020-09-07 20:16:16 +02:00
Martin Pépin 2c833daa7f
404.html: English version + tighter header 2020-09-07 20:10:34 +02:00
Martin Pepin 2a05c2247c
Apply suggestion to gestioncof/templates/404.html 2020-09-07 20:10:34 +02:00
Martin Pépin c957ab2b72
Add K-Psul on the 404 page 2020-09-07 20:10:34 +02:00
Martin Pépin 98cce25f4c
cofsite-looking 404 ? 2020-09-07 20:10:26 +02:00
Martin Pepin ba6ddfc516 Merge branch 'Aufinal/kfet-auth' into 'master'
Groupes et perms K-Fêt

See merge request klub-dev-ens/gestioCOF!438
2020-09-07 20:09:19 +02:00
Ludovic Stephan c5d7eb9d30 Move permission handling to loadkfetdevdata 2020-09-07 14:57:41 +02:00
Ludovic Stephan d3185f25c3 Black 2020-09-07 14:57:41 +02:00
Ludovic Stephan 007b5006d4 Use convenience imports 2020-09-07 14:57:41 +02:00
Ludovic Stephan 2d36c85085 Fix dev data 2020-09-07 14:57:41 +02:00
Ludovic Stephan d6fa738a25 Fix tests 2020-09-07 14:57:41 +02:00
Ludovic Stephan c145191e55 Use new models and mixins 2020-09-07 14:57:41 +02:00
Ludovic Stephan 91852bd4a0 Template fixes 2020-09-07 14:57:41 +02:00
Ludovic Stephan 6f5fa19fc3 M2M form mixin 2020-09-07 14:57:41 +02:00
Ludovic Stephan e92d50593c New models 2020-09-07 14:57:41 +02:00
Ludovic Stephan 0590bc3aab Merge branch 'kerl/kfet_autocomplete_fix' into 'master'
Remets le lien pour inscrire des nouveaux comptes en K-Fêt sur la page d'autocomplétion

See merge request klub-dev-ens/gestioCOF!456
2020-09-07 14:43:04 +02:00
Martin Pépin 3286ad09df
Update CHANGELOG 2020-09-07 11:35:25 +02:00
Martin Pépin 3da0a613f7
K-Fêt autocompletion shows the 'new user' link 2020-09-07 11:32:28 +02:00
Ludovic Stephan 34be9e2393 Merge branch 'kerl/fix_exte_login_bug' into 'master'
Meilleure gestion des erreurs dans le formulaire de login Exté → pas de crash

See merge request klub-dev-ens/gestioCOF!455
2020-09-06 20:59:48 +02:00
Martin Pépin 97bdeed97a
Prevent a crash in exte login form error handling 2020-09-05 23:53:31 +02:00
Martin Pépin 8016b16904
Version 0.7.1 2020-09-05 00:02:28 +02:00
Ludovic Stephan 6e8926595d Merge branch 'kerl/gestion_prefix' into 'master'
Ajoute le préfixe /gestion dans toutes les urls sauf celles de la K-Fêt et de Wagtail

Closes #256

See merge request klub-dev-ens/gestioCOF!450
2020-09-02 23:45:34 +02:00
Ludovic Stephan a9b6bc65a2 Merge branch 'kerl/discard_weird_ldap_users' into 'master'
Ignore les comptes LDAP bizarres (e.g. root)

See merge request klub-dev-ens/gestioCOF!453
2020-09-02 23:44:33 +02:00
Martin Pépin b1fd6e6021
Discard the (weird) ldap accounts that have no uid 2020-09-02 21:34:21 +02:00
Martin Pepin 576d43f44d Merge branch 'sakarah/club-email-bug' into 'master'
Replace all "pont" by "." in COF clubs emails

See merge request klub-dev-ens/gestioCOF!452
2020-09-02 20:42:36 +02:00
Martin Pépin c55a2c8c8e
Update changelog 2020-09-02 20:35:17 +02:00
Guillaume Bertholon 65c979ea59
Replace all "pont" by "." in COF clubs emails
This patches wrongly displayed "Contact : fromages@lists.enspontfr" on
https://cof.ens.fr/gestion/sitecof/annuaires-des-clubs/
2020-09-02 20:34:25 +02:00
Martin Pépin dcd592ed11
Fix 100 tests wrt. 754a0b70e (big url changes) 2020-09-02 20:28:19 +02:00
Martin Pépin e401303a08
User-friendly redirect : / → /gestion 2020-09-02 20:25:46 +02:00
Martin Pépin 2b72f3b40b
All pages go under /gestion except wagtail & k-fet 2020-09-02 20:23:11 +02:00
Martin Pepin 2aae281120 Merge branch 'Aufinal/urls' into 'master'
Améliore les URLs de gestiocof

Closes #265

See merge request klub-dev-ens/gestioCOF!439
2020-09-02 20:15:12 +02:00
Ludovic Stephan 1387da3b54
black 20 2020-09-02 20:06:28 +02:00
Ludovic Stephan e868e6eb18
No bds prefix for prod 2020-09-02 20:06:28 +02:00
Ludovic Stephan fc988e3fad
Fix isort 2020-09-02 20:06:28 +02:00
Ludovic Stephan 205b5c206b
Fix tests 2020-09-02 20:06:28 +02:00
Ludovic Stephan ab9b4d14ef
Some changes to mega urls 2020-09-02 20:06:28 +02:00
Ludovic Stephan 5f8b8661bf
Better URL management 2020-09-02 20:06:28 +02:00
Ludovic Stephan 858a0c61e2
Rend à gestiocof ce qui est à gestiocof 2020-09-02 20:06:27 +02:00
Martin Pepin 12d3ef0c02 Merge branch 'Aufinal/wakemeup' into 'master'
On est en septembre !

See merge request klub-dev-ens/gestioCOF!451
2020-09-02 19:57:04 +02:00
Ludovic Stephan da40ed1d8c Migration 2020-09-01 15:27:28 +02:00
Martin Pépin f10bd1eea2
Update changelog 2020-08-30 12:48:51 +02:00
Martin Pepin 8576023b42 Merge branch 'Evarin/css-sitecof' into 'master'
Améliorations CSS + directoryentrypage sitecof encore

See merge request klub-dev-ens/gestioCOF!447
2020-08-30 12:47:11 +02:00
Evarin 24eaaa277f Sitecof : Affiche titre des pages dans <title> 2020-08-30 12:11:13 +02:00
Evarin 8f4e3bb048 isort linting migration 2020-08-29 23:39:31 +02:00
Evarin 7775e45b60 Black linting migration 2020-08-29 23:23:52 +02:00
Evarin b3ada0eb89 Sitecof css modifs mineures 2020-08-29 23:21:20 +02:00
Evarin 74b9721fbd Sitecof : Champ libre dans directoryentry 2020-08-29 23:15:28 +02:00
Evarin 7db75c0060 Sitecof CSS ++ 2020-08-29 23:14:19 +02:00
Martin Pepin d96b3d26b6 Merge branch 'Evarin/css-sitecof' into 'master'
Améliorations mineures CSS sitecof

See merge request klub-dev-ens/gestioCOF!446
2020-08-29 22:54:29 +02:00
Evarin 72210e1980 Sitecof : améliogrations CSS 2020-08-29 22:18:32 +02:00
Evarin 359f85a42d Sitecof : CSS fixes 2020-08-29 21:34:28 +02:00
Martin Pépin 95cac47f4e
Version 0.7 2020-08-29 19:28:38 +02:00
Martin Pepin 7dc32add35 Merge branch 'Aufinal/kfet_autocomplete' into 'master'
Rajoute un nombre min de caractères pour la création d'un compte

See merge request klub-dev-ens/gestioCOF!440
2020-08-29 12:25:43 +02:00
Martin Pepin 0011cfe8f7 Merge branch 'Aufinal/bds_perms' into 'master'
Le groupe BDS a des permissions normales

Closes #263

See merge request klub-dev-ens/gestioCOF!444
2020-08-29 12:24:35 +02:00
Ludovic Stephan 5a7c4f64d5 changelog 2020-08-28 18:46:13 +02:00
Ludovic Stephan 46893a8df5 Min chars for autocompletion 2020-08-28 18:45:07 +02:00
Ludovic Stephan 81b45f74e3 changelog 2020-08-28 18:42:37 +02:00
Ludovic Stephan c6dfcea5e2 Remove signals import 2020-08-28 18:41:40 +02:00
Ludovic Stephan 198e456c22 Fix BDS group perms 2020-08-28 18:41:40 +02:00
Martin Pepin 1518f4c703 Merge branch 'Aufinal/misc_bds' into 'master'
Quelques améliorations pour le BDS

Closes #270

See merge request klub-dev-ens/gestioCOF!443
2020-08-28 18:38:49 +02:00
Martin Pépin 2d59565f61
Update changelog 2020-08-28 18:23:20 +02:00
Ludovic Stephan d3384dc5fc
Remove context processor 2020-08-28 18:23:20 +02:00
Ludovic Stephan e7cc705350
Add member count to home 2020-08-28 18:23:20 +02:00
Ludovic Stephan 8fa635773c
Notifications are closable 2020-08-28 18:23:20 +02:00
Ludovic Stephan 1ac3e0f976
Plug logout link 2020-08-28 18:23:19 +02:00
Ludovic Stephan f811230c25
Add comment field 2020-08-28 18:23:19 +02:00
Martin Pepin 74c3afe9ca Merge branch 'Aufinal/black20' into 'master'
Fix la CI pour black

See merge request klub-dev-ens/gestioCOF!445
2020-08-28 18:18:15 +02:00
Ludovic Stephan 0875ef1278 Black v20 2020-08-28 18:00:54 +02:00
Martin Pepin 1d707aad41 Merge branch 'Aufinal/delete_bds_user' into 'master'
Possibilité de supprimer un utilisateur sur gestioBDS

Closes #271

See merge request klub-dev-ens/gestioCOF!442
2020-08-25 20:17:51 +02:00
Martin Pépin fef19024d8
Update changelog 2020-08-25 20:04:26 +02:00
Ludovic Stephan 566e968849
Fix cance button 2020-08-25 20:04:26 +02:00
Ludovic Stephan 55c69ae42b
Styling 2020-08-25 20:04:26 +02:00
Ludovic Stephan 40839458a5
Form logic for user deletion 2020-08-25 20:04:26 +02:00
Ludovic Stephan a259dd524f
UserDelete view 2020-08-25 20:04:26 +02:00
Ludovic Stephan 85c750d380
Delete unused template 2020-08-25 20:04:26 +02:00
Martin Pépin 826e45f619
Move CaptchaFrom from views.py to forms.py 2020-08-25 19:24:54 +02:00
Martin Pépin 5989f65154
Fix linting issues 2020-08-25 19:24:02 +02:00
Martin Pepin 2cf0ccbb6b Merge branch 'Aufinal/ldap_mail' into 'master'
Utilise le mail LDAP lors de l'inscription d'utilisateurs

Closes #268

See merge request klub-dev-ens/gestioCOF!437
2020-08-25 19:08:25 +02:00
Ludovic Stephan 1677768177 Merge branch 'kerl/bds_authens' into 'master'
Authens pour le BDS

See merge request klub-dev-ens/gestioCOF!441
2020-08-24 15:38:24 +02:00
Martin Pépin 7a7e02adab
Bump authens to 0.1b0 2020-08-24 15:30:50 +02:00
Martin Pépin 62d26560d9
Fix BDS {MEDIA,STATIC}_{URL,ROOT} 2020-08-24 14:56:26 +02:00
Martin Pépin df9639715b
Move COF-specific settings (channels) to common.py 2020-08-24 14:55:27 +02:00
Evarin 72237fef60 Sitecof : captcha pour les listes mail 2020-08-22 12:34:08 +02:00
Ludovic Stephan 8fa07bb845 Fix tests 2020-08-03 14:54:58 +02:00
Ludovic Stephan 7931f50611 Use ldap email 2020-08-03 14:30:21 +02:00
Ludovic Stephan 910536c6d3 Add email to ldap autocomplete 2020-08-03 14:30:12 +02:00
Martin Pépin 3d830884b1
Use authens in GestioBDS 2020-07-30 12:04:04 +02:00
Martin Pépin 9110e5b185
Update changelog 2020-07-27 23:10:18 +02:00
Ludovic Stephan 22f60163fe Merge branch 'kerl/bds_rm_certificat_medical' into 'master'
Le BDS n'a pas besoin du certificat médical

See merge request klub-dev-ens/gestioCOF!435
2020-07-27 22:41:14 +02:00
Martin Pépin 8661716df9
BDS doesn't need the certificate file 2020-07-27 22:14:20 +02:00
Martin Pepin ae64f09869 Merge branch 'Aufinal/bds_create_user' into 'master'
Création d'utilisateurs pour le BDS

See merge request klub-dev-ens/gestioCOF!433
2020-07-27 21:41:12 +02:00
Ludovic Stephan 422e2f7b42 Fix date input 2020-07-26 22:34:56 +02:00
Ludovic Stephan f990934425 On utilise un vrai dict 2020-07-26 22:24:41 +02:00
Ludovic Stephan effed1b5c5 Fix template 2020-07-26 22:12:38 +02:00
Ludovic Stephan efbb9c2be3 Encore plus de doc 2020-07-26 22:10:09 +02:00
Ludovic Stephan a6c9cf11bd Meilleure doc 2020-07-26 22:10:09 +02:00
Ludovic Stephan 26fa9dc898 Add create user from scratch 2020-07-26 22:10:09 +02:00
Ludovic Stephan ee1d158f2d Plug into autocomplete and urls 2020-07-26 22:10:09 +02:00
Ludovic Stephan 5e5b224f89 User creation views 2020-07-26 22:10:09 +02:00
Ludovic Stephan f33416b712 Use mixin in UserUpdateView 2020-07-26 22:10:09 +02:00
Ludovic Stephan b6626093e5 Mixin pour forms multiples 2020-07-26 22:10:09 +02:00
Martin Pepin 15936751c0 Merge branch 'Aufinal/bulma_bds' into 'master'
CSS pour le BDS avec Bulma

See merge request klub-dev-ens/gestioCOF!432
2020-07-26 19:37:40 +02:00
Ludovic Stephan 9efc200f74 Fusionne base et base_layout 2020-07-26 19:24:40 +02:00
Ludovic Stephan 54e8f95667 Logout button fix 2020-07-26 19:06:38 +02:00
Ludovic Stephan a5ccd40ec1 Merge branch 'kerl/prod_hotfix' into 'master'
Bump some channels/redis requirements

See merge request klub-dev-ens/gestioCOF!434
2020-07-26 17:24:38 +02:00
Martin Pépin b4fbc3edf8
BDS CSS: use plain black for text 2020-07-26 17:17:20 +02:00
Martin Pépin eb10c904e0
Bump some channels/redis requirements 2020-07-25 22:18:43 +02:00
Ludovic Stephan c8c8c6abc8 Message fixes 2020-07-20 19:06:19 +02:00
Ludovic Stephan e64f405299 Tweaks 2020-07-20 11:34:28 +02:00
Ludovic Stephan aa2f691f1e Chromium support 2020-07-20 11:30:31 +02:00
Ludovic Stephan 8cd9434664 Fix autocomplete width 2020-07-20 11:30:31 +02:00
Ludovic Stephan e323f2f755 Bulmafy navbar 2020-07-20 11:30:31 +02:00
Ludovic Stephan deae1c4639 FontAwesome : gestioncof -> shared 2020-07-20 11:30:31 +02:00
Ludovic Stephan 62281cb3b7 Templates update 2020-07-20 11:30:31 +02:00
Ludovic Stephan 6454931e70 bds is now in scss 2020-07-20 11:30:31 +02:00
Ludovic Stephan 6e88f1a887 Form utils for bulma 2020-07-20 11:30:31 +02:00
Ludovic Stephan 2e28986503 Bulma files 2020-07-20 11:30:31 +02:00
Ludovic Stephan b24935b938 Merge branch 'kerl/bds_update_user' into 'master'
BDS: vue pour modifier un compte existant

See merge request klub-dev-ens/gestioCOF!430
2020-07-20 11:25:01 +02:00
Martin Pépin ac06211841 Make bds tests resilient to LOGIN_URL changes 2020-07-20 11:12:01 +02:00
Martin Pépin 5c1e2e9cda Basic tests for BDS registration views 2020-07-20 11:12:01 +02:00
Martin Pépin c1e48579f1 BDS: UserUpdateView 2020-07-20 11:11:53 +02:00
Ludovic Stephan c6a6e7fafa Merge branch 'kerl/factor_autocompletion_views3' into 'master'
Vue et template génériques d'autocomplétion

See merge request klub-dev-ens/gestioCOF!429
2020-07-18 17:46:04 +02:00
Martin Pépin be064262da
Fix kfet autocompletion hightlighting 2020-07-18 16:24:07 +02:00
Martin Pépin 9ac030fd16
Instantiate the Compose classes in their own file 2020-07-18 16:07:12 +02:00
Martin Pépin 7caee5665b
Make isort happy… 2020-07-18 16:07:12 +02:00
Martin Pépin e7517195cd
Generic autocompletion view 2020-07-18 16:07:12 +02:00
Martin Pépin 30783d677b
Minor changelog update, version 0.5 2020-07-11 10:09:25 +02:00
Martin Pépin c863b2010e
Update changelog 2020-07-05 20:06:33 +02:00
Ludovic Stephan 24d7d90c28 Merge branch 'kerl/factor_autocompletion_views2' into 'master'
Petite réorganisation de l'autocomplétion

See merge request klub-dev-ens/gestioCOF!428
2020-07-05 18:20:26 +02:00
Martin Pépin f2b1962e1c
Autocompletion: more idiomatic permission handling 2020-07-05 16:38:59 +02:00
Ludovic Stephan 68ccd4722f Merge branch 'kerl/factor_autocompletion_views1' into 'master'
Mécanisme de dé-duplication des résultats plus souple pour l'autocomplétion

See merge request klub-dev-ens/gestioCOF!427
2020-07-05 11:25:35 +02:00
Martin Pépin 9a90f19502
Separate the autocompletion logic form the views 2020-07-05 11:15:50 +02:00
Martin Pépin fbbc9937f6
Fix a typo 2020-07-05 11:14:51 +02:00
Martin Pépin e9f00b4f06
Update the isort config for version 5.* 2020-07-04 13:40:32 +02:00
Martin Pépin c7ca96bce5
Autocompletion: new de-duplication mechanism 2020-07-04 13:06:24 +02:00
Ludovic Stephan 637572ab58 Merge branch 'kerl/bds_autocomplete' into 'master'
Autocomplétion du BDS et deuxième ébauche de page d'accueil

See merge request klub-dev-ens/gestioCOF!422
2020-07-01 23:26:01 +02:00
Ludovic Stephan 28370c8e67 Merge branch 'kerl/bds_settings' into 'master'
Séparation des settings cof / bds

See merge request klub-dev-ens/gestioCOF!420
2020-06-30 12:47:46 +02:00
Martin Pépin 701ea96a90
BDS autocompletion: add missing span in html 2020-06-29 20:49:02 +02:00
Martin Pépin de1bba3695
Don't crash on LDAP errors 2020-06-29 20:47:36 +02:00
Martin Pépin 56f1edebe3
BDS: fancier home page 2020-06-29 20:47:36 +02:00
Martin Pépin c52bac05b3
Restrict bds views to the staff 2020-06-29 20:47:36 +02:00
Martin Pépin 5d24786e20
BDS: user search on the home page 2020-06-29 20:47:35 +02:00
Martin Pépin bca75dbf98
Add user-search in the BDS app 2020-06-29 20:47:35 +02:00
Martin Pépin 0789da7bed
Move the 'utils' template tags to the shared app 2020-06-29 20:47:35 +02:00
Martin Pépin f6458074b2
Better documentation for show_toobar 2020-06-29 20:45:52 +02:00
Martin Pépin eadfd1d3cd
Use cof.settings.local for migration checks 2020-06-29 20:45:52 +02:00
Martin Pépin 3a34ab4462
Make events tests independent of LOGIN_URL 2020-06-29 20:45:52 +02:00
Martin Pépin 25b603d667
only run relevant tests in cof/bds CI 2020-06-29 20:45:52 +02:00
Martin Pépin f26d330973
Fix settings.local.ALLOWED_HOSTS 2020-06-29 20:45:52 +02:00
Martin Pépin 7a52e841e6
Use the new settings in gitlab-ci 2020-06-29 20:45:52 +02:00
Martin Pépin 9a3914ece6
Add wsgi file 2020-06-29 20:45:52 +02:00
Martin Pépin 6a32a72c15
One url file to rule them all,
one url file to find them
One url file to bring them all,
and in the darkness bind them.
2020-06-29 20:45:52 +02:00
Martin Pépin d464b69b2e
Split settings between COF / BDS / Local 2020-06-29 20:45:52 +02:00
Martin Pépin d16bf5e6b0
Merge local and dev settings 2020-06-29 20:45:52 +02:00
Ludovic Stephan 1ba6b5753f Merge branch 'kerl/fix_kfet_autocomplete' into 'master'
Passe à `shared.views.autocomplete` pour l'autocomplétion de la K-Fêt

See merge request klub-dev-ens/gestioCOF!425
2020-06-25 17:16:59 +02:00
Ludovic Stephan 21fcb5daa9 Merge branch 'kerl/fix_ldap' into 'master'
Switch to python-ldap (instead of ldap3)

Closes #264

See merge request klub-dev-ens/gestioCOF!424
2020-06-25 01:20:40 +02:00
Martin Pépin c5adc6b7d8
Use the new shared autocomplete framework in kfet/ 2020-06-20 19:28:48 +02:00
Martin Pépin b9ba0a3829
Add missing ldap system dependencies to CI config 2020-06-20 19:08:20 +02:00
Martin Pépin 028b6f6cb7
Switch to python-ldap (instead of ldap3) 2020-06-16 17:21:59 +02:00
Ludovic Stephan 3ca8b45014 Migration for events app 2020-05-20 17:41:25 +02:00
Ludovic Stephan 90fc6aa3e7 Merge branch 'Aufinal/simplify_tests' into 'master'
Utilitaire de tests simplifié

See merge request klub-dev-ens/gestioCOF!421
2020-05-15 16:12:47 +02:00
Martin Pépin 707b7b76db Make events tests deterministic 2020-05-14 21:23:25 +02:00
Ludovic Stephan 6fff995ccd Expand CSVResponseMixin functionality 2020-05-12 01:12:19 +02:00
Ludovic Stephan 9b0440429c Fix ical tests 2020-05-12 00:47:48 +02:00
Ludovic Stephan 50266f2466 Fix tests for python3.7 (?) 2020-05-11 13:03:13 +02:00
Ludovic Stephan 65171d1276 Fix event tests 2020-05-11 01:16:58 +02:00
Ludovic Stephan 3b43ad84b5 Renomme testcases.py -> mixins.py 2020-05-11 00:19:43 +02:00
Ludovic Stephan bb72a16b64 Lisibilité: t_urls -> reversed_urls 2020-05-10 23:58:46 +02:00
Ludovic Stephan b1c69eddb5 Meilleure doc (j'espère !) 2020-05-10 23:58:13 +02:00
Ludovic Stephan 88c9187e2e MegaHelpers devient un mixin 2020-05-10 23:56:45 +02:00
Ludovic Stephan bbe831a226 Sépare un gros fourre-tout en plus petits mixins 2020-05-10 23:54:21 +02:00
Ludovic Stephan f642b218d0 Consistance dans les noms de fichiers 2020-05-10 23:44:02 +02:00
Ludovic Stephan cc72f47f00 Merge branch 'kerl/event_options_and_extra_fields' into 'master'
Les événements du nouveau module `events` récupèrent les même fonctionnalités que les événements de `gestioncof`

See merge request klub-dev-ens/gestioCOF!398
2020-05-10 00:53:27 +02:00
Martin Pépin 24180e747e Events: one more validation check 2020-05-08 16:40:18 +02:00
Martin Pépin 5a0cf58d8a Events: more validation & uniqueness constraints 2020-05-08 16:34:35 +02:00
Martin Pépin d7d4d73af3 typos 2020-05-08 16:34:19 +02:00
Martin Pépin c2f6622a9f Update changelog 2020-05-08 16:16:37 +02:00
Martin Pépin 8778695e95 Add some more documentation in events.models 2020-05-08 16:14:04 +02:00
Martin Pépin e0fd3db638 Make events tests deterministic 2020-05-08 16:14:04 +02:00
Martin Pépin d5e9d09044 Events are configurable
This commit mostly reproduces the structure of gestioncof's events,
renames some stuff and adds a generic export view.
2020-05-08 16:14:04 +02:00
Martin Pepin 6e9dc03bc7 Merge branch 'Evarin/sitecof-improvements' into 'master'
Améliorations site du COF

See merge request klub-dev-ens/gestioCOF!415
2020-05-08 16:13:18 +02:00
Martin Pépin 6384cfc701 Update changelog 2020-05-08 16:04:05 +02:00
Martin Pépin 67d7dafc14 Merge branch 'master' into Evarin/sitecof-improvements 2020-05-08 15:56:42 +02:00
Martin Pépin 1ada8645b8 Black 2020-05-08 15:52:13 +02:00
Ludovic Stephan d4a9e96e38 Merge branch 'kerl/ci' into 'master'
Bump python and postrgres in CI

See merge request klub-dev-ens/gestioCOF!419
2020-05-08 13:06:29 +02:00
Martin Pépin abb8cc5a2d Bump python and postrgres in CI 2020-05-08 12:47:03 +02:00
Martin Pepin 5a9ea4234e Merge branch 'Aufinal/simplify_stats' into 'master'
Simplifie massivement les statistiques K-Fêt + étend la période de stats

Closes #257 and #244

See merge request klub-dev-ens/gestioCOF!411
2020-05-08 12:41:18 +02:00
Ludovic Stephan c9136dbcfa CHANGELOG 2020-05-08 11:15:12 +02:00
Ludovic Stephan 61e4ad9741 Better docstring 2020-05-08 11:14:32 +02:00
Ludovic Stephan c9dad9465a Fix tests 2020-05-08 11:14:32 +02:00
Ludovic Stephan f10d6d1a71 Bugfix
Quand un article n'a pas de conso, il a été créé il y a 1s
2020-05-08 11:14:32 +02:00
Ludovic Stephan 97cb9d1f3b Rework stats_manifest
On change la façon dont les vues gèrent l'interface avec `Scale`.
Side effect : on peut avoir l'historique sur tout le temps
2020-05-08 11:14:32 +02:00
Ludovic Stephan c66fb7eb6f Simplify statistic.js
On supprime des fonctions inutiles, on lint, et on simplifie 2-3 options
inutilisées.
2020-05-08 11:14:32 +02:00
Ludovic Stephan 48ad5cd1c7 Misc cleanup
On utilise SingleObjectMixin partout, et on simplifie 2-3 trucs
2020-05-08 11:14:32 +02:00
Ludovic Stephan ef35f45ad2 Fusionne deux fonctions chunkify
On rajoute de l'agrégation optionnelle dans la fonction.
2020-05-08 11:14:32 +02:00
Ludovic Stephan 26bcd729bb Supprime le code mort ou redondant 2020-05-08 11:14:32 +02:00
Ludovic Stephan 78ad4402b0 Plus de timezones 2020-05-08 11:14:32 +02:00
Ludovic Stephan 6767ba8e8c Rajoute de la doc partout 2020-05-08 11:14:32 +02:00
Ludovic Stephan 4f15bb9624 CHANGELOG 2020-05-07 18:40:07 +02:00
Martin Pepin 3b2251a1d6 Merge branch 'Aufinal/editable_accounts' into 'master'
Harmonise les comptes non-lisibles ou éditables

Closes #234

See merge request klub-dev-ens/gestioCOF!412
2020-05-07 18:07:07 +02:00
Ludovic Stephan 64ceb813c6 Merge branch 'kerl/autocomplete' into 'master'
L'autocomplétion est isolée et réutilisable par d'autres apps

See merge request klub-dev-ens/gestioCOF!390
2020-05-07 16:28:46 +02:00
Martin Pépin 3b0d4ba58f lstephan's suggestions 2020-05-07 15:44:37 +02:00
Ludovic Stephan 5298a19667 Merge branch 'kerl/merge_shared_utils' into 'master'
Préparation de la vue d'autocompletion pour l'intégration du BDS

See merge request klub-dev-ens/gestioCOF!401
2020-05-07 14:54:50 +02:00
Martin Pépin b1d8bb04c4 Generic auto-completion mechanism 2020-05-07 14:48:37 +02:00
Martin Pépin b8cd5f1da5
Drop type hints in shared.views.autocomplete 2020-05-05 22:30:17 +02:00
Martin Pépin a259b04d9c
Explicative comment about the Type[M] annotation 2020-05-05 22:30:17 +02:00
Martin Pépin e45ee3fb40
More documentation for ModelSearch 2020-05-05 22:30:17 +02:00
Martin Pépin d2c6c9da7a
Type hints in shared.views.autocomplete 2020-05-05 22:30:16 +02:00
Martin Pépin 914888d18a
Merge the utils and shared apps 2020-05-05 22:30:16 +02:00
Ludovic Stephan c8b8c90580 CHANGELOG 2020-04-24 21:03:16 +02:00
Antonin Reitz 922190d20f Merge branch 'Aufinal/transferts_historique' into 'master'
Rajoute les transferts dans l'historique

Closes #77 and #233

See merge request klub-dev-ens/gestioCOF!399
2020-04-23 18:46:30 +02:00
Ludovic Stephan 6362740a77 Fix: history.html marche (à peu près) correctement 2020-04-23 18:11:23 +02:00
Ludovic Stephan 9eebc7fb22 Fix: les transferts apparaissent dans l'historique perso 2020-04-23 18:11:23 +02:00
Ludovic Stephan 2aa06d2954 Simplify transfer view 2020-04-23 18:11:23 +02:00
Ludovic Stephan 931b2c4e1f Refactor js code
Harmonize history denominations
* opegroups/transfergroups -> groups
* opes/transfers -> entries
* snake/camel case -> snake case
2020-04-23 18:11:23 +02:00
Ludovic Stephan b450cb09e6 Petit refactor 2020-04-23 18:11:23 +02:00
Ludovic Stephan 8d11044610 Fix: pas d'erreur quand pas de compte K-Fêt 2020-04-23 18:11:23 +02:00
Ludovic Stephan 786c8f132f Fix: tests cassés par commit précédent 2020-04-23 18:11:23 +02:00
Ludovic Stephan 677ba5b92e Fix : le ws K-Psul remarche 2020-04-23 18:11:23 +02:00
Ludovic Stephan fb4455af39 Fix tests 3 2020-04-23 18:11:23 +02:00
Ludovic Stephan 7438445110 Last tweaks 2020-04-23 18:11:23 +02:00
Ludovic Stephan f7ce2edd87 Plug new history in templates 2020-04-23 18:11:23 +02:00
Ludovic Stephan 0221221d53 On renvoie les promesses 2020-04-23 18:11:23 +02:00
Ludovic Stephan 49ef8b3c15 Pas besoin de ws pour les suppressions 2020-04-23 18:11:23 +02:00
Ludovic Stephan 550a073d51 Fix tests again 2020-04-23 18:11:23 +02:00
Ludovic Stephan af0de33d4c Suppression des opérations et des transferts 2020-04-23 18:11:23 +02:00
Ludovic Stephan c95e1818b2 Fix ws tests 2020-04-23 18:11:23 +02:00
Ludovic Stephan 41ad2a15ac Update websocket data 2020-04-23 18:11:23 +02:00
Ludovic Stephan 36d6a4a1cd Déplace la logique de l'historique dans history.js
On change le lock en `window.lock` pour y avoir accès partout
2020-04-23 18:11:23 +02:00
Ludovic Stephan 9b2c4c1f98 Change l'affichage de la date dans l'historique
Fixes #233
2020-04-23 18:11:23 +02:00
Ludovic Stephan c3b5de336a Gère l'affichage des transferts dans l'historique 2020-04-23 18:11:23 +02:00
Ludovic Stephan bf117ec070 Renvoie les transferts dans l'historique 2020-04-23 18:11:23 +02:00
Ludovic Stephan a3b0ea9b8d Fetch transfers in history_json 2020-04-23 18:11:23 +02:00
Evarin 9dabab51db I18n 2020-03-29 16:11:02 +02:00
Evarin 2ad400c5e7 Fixes interface cofcms 2020-03-29 15:36:47 +02:00
Evarin 8a27f70e89 Limite à 4 news sur la page d'accueil 2020-03-29 15:36:19 +02:00
Robin Champenois fcf29fe6df Merge branch 'sakarah/webfonts-sitecof' into 'master'
Servir les polices de sitecof en local

See merge request klub-dev-ens/gestioCOF!413
2020-03-29 11:50:28 +02:00
Robin Champenois 31e4658766 Merge branch 'sakarah/patch-jquery-path' into 'master'
Corrige les chemins vers jquery pour sitecof

See merge request klub-dev-ens/gestioCOF!414
2020-03-29 11:50:14 +02:00
Guillaume Bertholon 7b554e4778 Corrige les chemins vers jquery pour sitecof 2020-03-28 14:10:24 +01:00
Guillaume Bertholon fe2f8aaa5a Servir les polices de sitecof en local
Le nouveau site du COF réintroduisait des fontes hostées chez Google.
On s'en débarasse en utilisant des webfontes locales.
2020-03-28 13:59:07 +01:00
Ludovic Stephan 137dd655d1 Harmonise les comptes non-lisibles ou éditables 2020-03-11 22:30:47 +01:00
Martin Pepin 494cd5ddc1 Merge branch 'kerl/sitecof_directory_entry_template' into 'master'
Template pour les entrées d'annuaire (sitecof)

See merge request klub-dev-ens/gestioCOF!394
2020-02-12 18:36:05 +01:00
Martin Pépin 80188fa88d
CMS club page: redirection to parent page 2020-02-12 18:20:33 +01:00
Martin Pépin 4580f8bf0f
Update changelog 2020-02-12 18:20:33 +01:00
Martin Pépin 03e6fe3ef6
Default template for cof directory entries 2020-02-12 18:18:56 +01:00
Martin Pepin 8c75189ce1 Merge branch 'Aufinal/article_charts' into 'master'
Fix: les articles ont de nouveau leur graphe d'usage

Closes #260

See merge request klub-dev-ens/gestioCOF!410
2020-02-08 11:19:50 +01:00
Martin Pépin 68b7219cf5
Update CHANGELOG 2020-02-08 11:06:34 +01:00
Ludovic Stephan 7a828760b3 Répercute les changements en prod 2020-02-08 10:47:55 +01:00
Martin Pépin 5280ec2d18
Update CHANGELOG 2020-01-27 21:17:00 +01:00
Martin Pepin b0d8b0b7f8 Merge branch 'Aufinal/ukf-display' into 'master'
Fix l'affichage des UKF dans K-Psul

Closes #259

See merge request klub-dev-ens/gestioCOF!409
2020-01-27 21:13:05 +01:00
Ludovic Stephan bc90de76b6 Fix l'affichage des UKF 2020-01-18 17:01:07 +01:00
Ludovic Stephan ed97ff466d Merge branch 'kerl/fix-bda-participant-email-list' into 'master'
Les boutons "afficher/cacher" les mails et noms des participant⋅e⋅s d'un spectable BdA fonctionnent à nouveau

See merge request klub-dev-ens/gestioCOF!408
2020-01-18 16:40:57 +01:00
Martin Pépin 6cce9779fa
participants.html: s/participants/participant⋅e⋅s/ 2020-01-18 12:23:45 +01:00
Martin Pépin 2c2872275a
Update CHANGELOG 2020-01-18 12:22:32 +01:00
Martin Pépin fb3f6b9073
Add missing <script> tag in bda/participants.html 2020-01-18 12:20:39 +01:00
Martin Pépin 28cb35e0b0
Version 0.4.1 2020-01-17 21:54:00 +01:00
Antonin Reitz 79ad1346d3 Merge branch 'Aufinal/hashtag_42' into 'master'
On supporte à nouveau les caractères d'urls dans les trigrammes

See merge request klub-dev-ens/gestioCOF!407
2020-01-17 00:42:25 +01:00
Ludovic Stephan bb05edfd6b CHANGELOG 2020-01-16 23:24:07 +01:00
Ludovic Stephan 4d3531c2cb Fix special chars in trigramme 2020-01-16 23:20:18 +01:00
Martin Pépin ff968b68b2
Version 0.4 2020-01-15 22:42:24 +01:00
Ludovic Stephan 84c36b9903 CHANGELOG 2020-01-09 10:43:07 +01:00
Ludovic Stephan 3088098a0a Merge branch 'master' into 'master'
Fixed images not showing up in petitscours

See merge request klub-dev-ens/gestioCOF!406
2020-01-09 10:40:14 +01:00
Julien Malka f9feff4b24 Wrong use of src -> replaced by vendor 2020-01-07 23:01:19 +01:00
Julien Malka 08d7e12c38 Fixed images not showing up in petitscours 2020-01-07 22:37:37 +01:00
Ludovic Stephan bd74f4098c Merge branch 'kerl/ci_python_37' into 'master'
CI : les tests tournent sous python 3.5 et python 3.7

See merge request klub-dev-ens/gestioCOF!402
2020-01-07 19:26:56 +01:00
Martin Pepin ee79281f53 Merge branch 'Aufinal/transfer_formset' into 'master'
Fix : nouveaux formulaires de transfert si le formset est plein

Closes #250

See merge request klub-dev-ens/gestioCOF!404
2020-01-04 16:49:42 +01:00
Martin Pépin ee4d2d7f0e
CI: run tests on python:3.5 and python:3.7 2020-01-04 16:35:13 +01:00
Martin Pépin f19b257afd
Update changelog 2020-01-04 16:33:32 +01:00
Ludovic Stephan 87e3795c76 Ajout d'un nouveau transfert si formulaire rempli 2020-01-04 15:31:14 +01:00
Ludovic Stephan f5d6d91e51 Merge branch 'kerl/happy_new_year' into 'master'
Happy new year!

See merge request klub-dev-ens/gestioCOF!403
2020-01-03 23:41:33 +01:00
Martin Pépin a1a2aac1f3
K-Fêt: new year, no valid promo… 2020-01-03 17:33:27 +01:00
Ludovic Stephan c1449d50ce Merge branch 'kerl/bds-buro' into 'master'
petite mise à jour de BDSProfile

See merge request klub-dev-ens/gestioCOF!396
2019-12-28 10:17:20 +01:00
Martin Pepin e13a5b0e60 Merge branch 'kerl/sitecof_clubs_optional_urls' into 'master'
Dans la description d'un club (ou d'un partenaire du COF), le link est optionel.

See merge request klub-dev-ens/gestioCOF!393
2019-12-26 23:27:20 +01:00
Martin Pépin 858759865e
BDSProfile: s/membre/adhérent⋅e/ 2019-12-26 23:19:41 +01:00
Martin Pépin 8bae013152
BDSProfile: add is_member & cotisation_type fields 2019-12-26 13:11:37 +01:00
Martin Pépin 4d5419fdbc
Use permissions to authenticate bds buro members
I prefer using a permission (namely `bds.is_team`) to determine if a
user is member of the BDS staff rather that using a `is_buro` boolean
field.

We already use this approach is the kfet app
2019-12-26 13:09:38 +01:00
Martin Pépin 2e4d7101ce
Update changelog 2019-12-26 01:03:46 +01:00
Martin Pépin 229b6e55f5
cofsite: make club links optional 2019-12-26 01:02:45 +01:00
Ludovic Stephan d2ba9471da Merge branch 'kerl/permission_disambiguation' into 'master'
Disambiguation in kfet's permission handling

See merge request klub-dev-ens/gestioCOF!397
2019-12-25 17:45:03 +01:00
Martin Pépin 1f945d1af3
Avoid using get_by_natural_key 2019-12-24 17:14:45 +01:00
Martin Pépin 64c792b11f
Disambiguation in kfet's permission handling
In some places we used to refer to permissions based on their codename
only (the part after the dot "." in the following examples) which can be
ambiguous. Typically, we might define permissions like "bds.is_team" or
"cof.is_team" in the near future ;)
2019-12-24 17:14:45 +01:00
Ludovic Stephan 67e28c704f Merge branch 'kerl/kfet_calendar_links' into 'master'
Met à jour l'url du calendrier de la K-Fêt sur la page d'accueil de GestioCOF

See merge request klub-dev-ens/gestioCOF!392
2019-12-23 11:32:23 +01:00
Martin Pépin ac901e5b77
Update changelog 2019-12-22 23:49:52 +01:00
Ludovic Stephan 21fc91c3a4 Merge branch 'kerl/rm_todo_prod' into 'master'
Remove the obsolete TODO_PROD file

See merge request klub-dev-ens/gestioCOF!395
2019-12-22 23:45:46 +01:00
Martin Pépin 00bad52570
Remove the obsolete TODO_PROD file 2019-12-20 17:49:55 +01:00
Martin Pépin 59d93900a3
Update the k-fet calendar url on the home page 2019-12-20 17:08:58 +01:00
Ludovic Stephan 2df4e931d4 Remove log 2019-12-18 21:15:40 +01:00
Ludovic Stephan 36e802082e Merge branch 'kerl/changelog' into 'master'
Petite mise à jour du CHANGELOG et changement de format

See merge request klub-dev-ens/gestioCOF!391
2019-12-13 00:38:05 +01:00
Martin Pépin d7e1583a8e
Nicer format for the CHANGELOG(.md) file 2019-12-12 22:02:57 +01:00
Martin Pépin 2c848a564c
Some changes were missing in CHANGELOG 2019-12-12 21:58:05 +01:00
Antonin Reitz e97c873b4f Merge branch 'Aufinal/backbone' into 'master'
Refactor le JS de K-Psul via Backbone : 1ère étape

See merge request klub-dev-ens/gestioCOF!388
2019-12-11 23:19:44 +01:00
Antonin Reitz f151ad75c6 For the sake of clarity 2019-12-11 23:05:39 +01:00
Antonin Reitz 83ce873e25 Remove unnecessary caching 2019-12-11 22:36:40 +01:00
Martin Pepin 71e3c210f2 Merge branch 'Aufinal/forgotten_decorators' into 'master'
Rajoute les décorateurs oubliés pour l'auth par mdp

See merge request klub-dev-ens/gestioCOF!389
2019-12-11 19:10:19 +01:00
Ludovic Stephan a4fdb578bc Add forgotten kfet_password decorators 2019-12-02 20:44:25 +01:00
Ludovic Stephan 0498db1140 Merge branch 'Aufinal/fix_created_paid' into 'master'
Fix: les participants nouvellement créés ont payé leurs places BdA

Closes #231

See merge request klub-dev-ens/gestioCOF!379
2019-12-02 09:59:15 +01:00
Martin Pépin 77ceae37ef
Update CHANGELOG 2019-12-01 11:37:43 +01:00
Martin Pépin 085013b256
Add some explanations about !379 2019-12-01 11:35:38 +01:00
Ludovic Stephan 381b52f46c
Fix: les participants nouvellement créés ont payé leurs places BdA
Si un participanti est créé avec `get_or_create`, son champ `paid`
n'était pas créé... C'est difficile à insérer dans la logique du
Manager, donc on fix ça dans la vue concernée.
2019-12-01 11:35:38 +01:00
Martin Pépin 0bd3bd63aa
Update changelog wrt last MR (!382) 2019-12-01 11:24:21 +01:00
Martin Pepin c6c4814519 Merge branch 'Aufinal/fix-stats-escape' into 'master'
Fix la page de stats pour certains comptes avec des caractères spéciaux

See merge request klub-dev-ens/gestioCOF!382
2019-12-01 11:22:36 +01:00
Martin Pépin b1747f61fe
Version 0.3.3 2019-11-30 19:04:56 +01:00
Ludovic Stephan 85aa56d030 Fix tests 2019-11-29 15:33:03 +01:00
Ludovic Stephan 361ad46be4 First steps in Account logic 2019-11-29 14:51:54 +01:00
Ludovic Stephan 4e15ab8041 Install django-js-reverse 2019-11-29 14:50:44 +01:00
Ludovic Stephan 091208b66c Make kfet.account.read.json accessible with GET 2019-11-29 14:47:12 +01:00
Ludovic Stephan 7df8a9ef6b Add vendor library and their sources 2019-11-28 18:26:39 +01:00
Martin Pépin 4c7993f48f
Forgot CHANGELOG for !385 2019-11-28 16:09:47 +01:00
Martin Pepin 94d5e0f0ac Merge branch 'Aufinal/fix_cas_redirect' into 'master'
Fix la redirection lors d'un logout CAS

See merge request klub-dev-ens/gestioCOF!385
2019-11-28 16:08:50 +01:00
Martin Pépin a521caba8d
Update changelog wrt the lastest merged patches. 2019-11-28 14:53:44 +01:00
Martin Pepin 797f0356f6 Merge branch 'Aufinal/no_reduction_category' into 'master'
Permet d'exclure des catégories de la réduction COF

See merge request klub-dev-ens/gestioCOF!386
2019-11-28 14:30:54 +01:00
Martin Pépin 8dcc1f012a
Update CHANGELOG 2019-11-28 14:17:59 +01:00
Ludovic Stephan 1115960107 Add unit test 2019-11-27 16:57:48 +01:00
Ludovic Stephan 727b3042a1 Merge branch 'kerl/fix-multiple-select-urls' into 'master'
Mise à jour de certaines urls (multiple-select.{css,js})

See merge request klub-dev-ens/gestioCOF!387
2019-11-27 16:09:13 +01:00
Martin Pépin 61efded673
Remove unused references to multiple-select.* 2019-11-27 15:46:50 +01:00
Ludovic Stephan 38aecdd741 Typo 2019-11-27 14:41:20 +01:00
Martin Pépin e0ffee295d
Fix static urls for multiple-select 2019-11-27 14:30:24 +01:00
Ludovic Stephan e62756ed29 Fix tests 2019-11-27 14:20:24 +01:00
Ludovic Stephan ac3bfbe368 Display in kfet js 2019-11-27 14:14:42 +01:00
Ludovic Stephan affdf43e0b Add logic in views and templates 2019-11-27 14:14:33 +01:00
Ludovic Stephan 20ceec0e64 Add has_reduction property 2019-11-27 14:11:53 +01:00
Ludovic Stephan 4c9ee8a57d Merge branch 'fix-cash-transaction-cancel' into 'master'
Fix typo and hence cash transaction cancel

Closes #239

See merge request klub-dev-ens/gestioCOF!384
2019-11-27 13:32:23 +01:00
Ludovic Stephan 5c581d8984 Cleanup + no msg on CAS logout 2019-11-27 13:14:20 +01:00
Ludovic Stephan ac4d5cf7d5 Patch CAS redirect parameter in logout view 2019-11-27 13:03:28 +01:00
Antonin Reitz b90e749a7f Fix typo and hence cash transaction cancel 2019-11-27 10:50:27 +01:00
Ludovic Stephan d04b79bcb5 Disable autoescape in js code 2019-11-25 10:48:43 +01:00
Ludovic Stephan 14164ec4a5 Merge branch 'kerl/fix-wagtail-deps' into 'master'
Règle un problème de dépendances de wagtailmenus avec python 3.8

See merge request klub-dev-ens/gestioCOF!381
2019-11-25 10:26:06 +01:00
Martin Pépin f9b461a08d
Bump django-redis-cache to version 2.1.* 2019-11-22 22:45:27 +08:00
Martin Pépin 8c5d09dbee
Bump wagtail to version 2.7 (LTS) 2019-11-22 22:35:23 +08:00
Martin Pépin e2af45929e
Use the production requirements in CI 2019-11-22 22:22:47 +08:00
Martin Pépin 481cb5e478
Move production deps out of requirements.txt 2019-11-22 22:18:04 +08:00
Martin Pépin a3ca2e66bf
Fix some dependency issue with wagtail 2019-11-22 22:18:04 +08:00
Martin Pepin 82746f1492 Merge branch 'Aufinal/fix_privilege_escalation' into 'master'
Fix le problème d'auth par mdp K-Fêt

Closes #240

See merge request klub-dev-ens/gestioCOF!380
2019-11-22 14:37:13 +01:00
Ludovic Stephan ea45eb1f55 Fix tests 2019-11-21 01:21:26 +01:00
Ludovic Stephan a60df91b04 Add decorator to needed views 2019-11-21 01:21:26 +01:00
Ludovic Stephan c1a99453d5 Add password auth decorator 2019-11-21 01:21:26 +01:00
Ludovic Stephan be5218f7e1 Remove pesky middleware 2019-11-21 01:21:26 +01:00
Ludovic Stephan dd5fe36ee1 Fix requirements problem 2019-11-21 01:21:26 +01:00
Ludovic Stephan 4a4dae9951 Merge branch 'kerl/clubs' into 'master'
Nouvelle app: clubs

See merge request klub-dev-ens/gestioCOF!372
2019-11-06 19:22:58 +01:00
Martin Pépin 5d87d7f249
Update CHANGELOG; version 0.3.2 2019-11-04 15:41:40 +01:00
Martin Pepin f9cde30e08 Merge branch 'Aufinal/fix_missing_names' into 'master'
Fix le problème des prénoms manquants

See merge request klub-dev-ens/gestioCOF!378
2019-11-04 15:39:35 +01:00
Ludovic Stephan b376114bee Fix tests 2019-11-03 00:36:57 +01:00
Ludovic Stephan 57088cda03 Fix le problème des prénoms manquants
Depuis !360, quand on modifie son propre compte K-Fêt, cela supprime les
noms associés sur gestioCOF... Le problème est réglé, normalement.
2019-11-03 00:02:34 +01:00
Ludovic Stephan b542f805f0 Merge branch 'kerl/empty_banner' into 'master'
Pour supprimer, la bannière "d'annonces" en haut de GestioCOF…

See merge request klub-dev-ens/gestioCOF!377
2019-10-21 16:28:20 +02:00
Martin Pépin 28a129f69c
Allows empty announcement banners 2019-10-19 21:17:13 +02:00
Martin Pépin c4f5e168f2
Update CHANGELOG for v0.3.1 2019-10-19 21:00:44 +02:00
Martin Pepin 5b2dad6fff Merge branch 'Aufinal/fix-accounts' into 'master'
Fix account history

See merge request klub-dev-ens/gestioCOF!376
2019-10-19 20:59:39 +02:00
Ludovic Stephan b22a77e603 Fix account history 2019-10-19 10:33:15 +02:00
Martin Pepin 4d9c66cb51 Merge branch 'Aufinal/fix-reventes' into 'master'
Fix: Souscription aux reventes

See merge request klub-dev-ens/gestioCOF!375
2019-10-18 09:31:56 +02:00
Ludovic Stephan 9254e3f8f7 Fix: Souscription aux reventes
Il me semblait que c'était déjà fait...
2019-10-17 10:50:54 +02:00
Martin Pepin 635c14ff8f Merge branch 'Aufinal/staticfiles' into 'master'
Réorganise les fichiers statiques de GestioCOF

See merge request klub-dev-ens/gestioCOF!352
2019-10-16 21:24:31 +02:00
Ludovic Stephan 898abd15c6 Ignore /src directories 2019-10-16 20:51:10 +02:00
Ludovic Stephan 337453c6c6 Add source for bootstrap-datetimepicker 2019-10-16 20:50:59 +02:00
Ludovic Stephan bdb8f06e1d Déplace bda.css et supprime une police
On met le static de `bda` dans `static/bda`, comme tout le monde.
2019-10-16 20:27:57 +02:00
Ludovic Stephan 64b4c2c08a Revert "Ignore src/ directories"
On ignorera ça proprement en Django 2.2...
2019-10-16 20:27:57 +02:00
Ludovic Stephan 8a6a3d0994 Ignore src/ directories 2019-10-16 20:27:57 +02:00
Ludovic Stephan 8ea0cb84d1 Move source files (not minified) to src/ directory 2019-10-16 20:27:57 +02:00
Ludovic Stephan 099857e226 Supprime une police inutilisée 2019-10-16 20:27:57 +02:00
Ludovic Stephan f8e954ff79 Range les fichiers statiques K-Fêt
Les fichiers JS et CSS externes sont dans `static/kfet/vendor`, minifiés ; on bump la version de `reconnecting-websocket`.
2019-10-16 20:27:57 +02:00
Ludovic Stephan 435bb392ea Déplace une fonction js 2019-10-16 20:27:57 +02:00
Ludovic Stephan de10392a7f Supprime des dossiers inutiles
Pas besoin de 12 sous-dossiers pour `autocomplete-light`.
2019-10-16 20:27:57 +02:00
Ludovic Stephan e421792906 Déplace les fichiers statiques de gestioncof
On met tous les fichiers de `gestioncof/static` dans `gestioncof/static/gestioncof`, comme pour les templates. On en profite pour virer pas mal de fichiers inutiles de `font-awesome`.
2019-10-16 20:27:57 +02:00
Ludovic Stephan abb3823a8b Interaction Moment.js + Chart.js
On avait pas besoin de servir `Chart.bundle.js` puisqu'on avait déjà `moment.js` servi à part. Aussi, on déplace tout et on sert les fichiers minifiés.
2019-10-16 20:27:57 +02:00
Ludovic Stephan b343c6c6e0 Déplace (et modifie) jquery-ui
On déplace les deux versions de `jquery-ui` dans `shared/static/vendor/`, et on en récupère une version avec seulement les fonctionnalités requises.
2019-10-16 20:27:57 +02:00
Ludovic Stephan 22cfaf9b44 Idem pour jquery
On met `jquery` dans `shared/static/vendor/`, et on bump un chouïa la version.
2019-10-16 20:27:57 +02:00
Ludovic Stephan a9dce881bd Supprime le CDN Bootstrap
On sert les fichiers nécessaires à `bootstrap` en local, dans `shared/static/vendor`.
2019-10-16 20:27:57 +02:00
Ludovic Stephan 0100a9a62e Move stupidtable import to base.html
Aussi, on déplace les fichiers JS associés dans `static/gestioncof/vendor/`
2019-10-16 20:27:57 +02:00
Martin Pépin 6fba63846a
Version 0.3 2019-10-16 20:12:07 +02:00
Martin Pépin 2a06cc5806
Update changelog 2019-10-16 19:58:12 +02:00
Martin Pépin 380e38519b
New app: clubs
- Clubs will be used both by the cof and the bds app.
- For now, they are only visible in development.
2019-10-16 19:58:12 +02:00
Ludovic Stephan 8ccaf8beaf Merge branch 'kerl/ci_missing_migrations' into 'master'
La CI regarde s'il manque des migrations

Closes #214

See merge request klub-dev-ens/gestioCOF!373
2019-10-16 19:55:29 +02:00
Ludovic Stephan 7892f42e3e Merge branch 'kerl/event_subscriptions' into 'master'
[Événements] Export des inscrits en csv

See merge request klub-dev-ens/gestioCOF!374
2019-10-16 19:48:36 +02:00
Martin Pépin 2964d3a4aa
K-FêT: new year = new promo = new migration 2019-10-16 19:43:20 +02:00
Martin Pépin 46da197507
CI: track missing migrations 2019-10-16 19:43:20 +02:00
Ludovic Stephan b005e772ea Merge branch 'kerl/bds' into 'master'
Début de l'app BDS (2)

See merge request klub-dev-ens/gestioCOF!371
2019-10-16 19:19:52 +02:00
Ludovic Stephan f83eeb7a5f Merge branch 'kerl/noncof_profile_and_passwd_change' into 'master'
Les utilisateurs non-COF peuvent changer leur mot de passe et éditer leur profil

Closes #178 and #177

See merge request klub-dev-ens/gestioCOF!368
2019-10-16 19:17:56 +02:00
Martin Pépin e94015a142
Update changelog 2019-10-15 21:56:48 +02:00
Martin Pépin 98fe68d0be
Translation fixes in bds.models
- the 'u' in ugettext_lazy in a legacy of python2, we can drop it now
- translate all verbose names
- start field verbose names with a lowercase letter
2019-10-15 21:54:52 +02:00
Martin Pépin f9aee86a1c
Only enable the bds app in development 2019-10-15 21:54:52 +02:00
Martin Pépin e2a7e1f6de
BDSProfile: enable the default admin 2019-10-15 21:53:04 +02:00
Martin Pépin 53efb4b542
Enable bds checks in CI 2019-10-15 21:53:04 +02:00
Ludovic Stephan 53ea6f24ee
Isort setup 2019-10-15 21:52:19 +02:00
Ludovic Stephan b3e7b59903
Migrations
Une migration pour les modèles, et une pour créer le groupe du Burô du
BDS
2019-10-15 21:51:40 +02:00
Ludovic Stephan bc7c30e2ee
Init app + models
Le modèle de profil BDS est le seul utile pour l'instant ; c'est un mix
entre `CofProfile` et les modèles de Sport@Ulm.
2019-10-15 21:51:40 +02:00
Ludovic Stephan 4da5add25a
Move choices_length to shared folder 2019-10-15 21:51:23 +02:00
Martin Pépin f5766e9207
events: make isort happy 2019-10-08 23:33:46 +02:00
Martin Pépin a8fd04e4c0
test events.views.participants_csv 2019-10-08 22:26:31 +02:00
Martin Pépin 33bc3c5882
Events: simple csv participants export 2019-10-08 22:26:30 +02:00
Martin Pépin 41a3c4c161
add event subscriptions (models only) 2019-10-08 22:21:37 +02:00
Martin Pépin 83c83d791b
Update changelog 2019-10-07 18:45:02 +02:00
Martin Pépin d5f0060e2e
Fix profile test: non-cof users can access /profile 2019-10-07 18:45:02 +02:00
Martin Pépin 41256154ad
Make profile editable for non-COF user
Non-COF users can now edit their own profile
Contrary to COF users they cannot change their mailing list settings
2019-10-07 18:45:02 +02:00
Martin Pépin 0814cfe1ef
home templates: add links for non-COF users
Add links to
- change password page
- profile page
2019-10-07 18:45:02 +02:00
Martin Pépin 730611039b
isort events.admin 2019-10-07 18:44:36 +02:00
Martin Pépin 9b355d5b56
linter config: add events to known_first_party 2019-10-07 18:32:51 +02:00
Martin Pépin 6781122fc1
Add the events app to the CI checks 2019-10-07 18:32:06 +02:00
Ludovic Stephan e9ca8eb8dd Merge branch 'kerl/refactor_events' into 'master'
Nouvelle app pour gérer les événements

See merge request klub-dev-ens/gestioCOF!365
2019-10-07 14:39:51 +02:00
Martin Pépin 97f682dfcd
Update changelog 2019-10-06 19:25:26 +02:00
Martin Pépin 9f2004bb54
Add events in the coverage report 2019-10-06 19:24:32 +02:00
Martin Pépin 8119591c62
enable the "events" app in CI 2019-10-06 19:24:32 +02:00
Martin Pépin 34e552f760
New 'events' app, first model
The objective is to move (at some point) all the management logic in
this app. Before that time: as long as the events app does not have all
the features necessary to be used in production it is only available in
dev mode and coexists with the old event system. When it's ready we'll
move the old events in the new app (data migration) and remove the old
system.
2019-10-06 19:24:32 +02:00
Ludovic Stephan ef97afd86c Merge branch 'kerl/pub-kde' into 'master'
Un peu de pub pour KDEns

See merge request klub-dev-ens/gestioCOF!367
2019-10-06 19:18:38 +02:00
Martin Pépin 9a7a447246
Update changelog 2019-10-06 17:54:12 +02:00
Martin Pépin f85f014bc9
Un peu de pub pour KDEns 2019-10-06 17:53:30 +02:00
Ludovic Stephan fb1519cece Merge branch 'kerl/unused-deps' into 'master'
Remove useless / unused dependencies

See merge request klub-dev-ens/gestioCOF!366
2019-10-06 13:50:43 +02:00
Ludovic Stephan 51e8058f97 Merge branch 'kerl/home_template_view' into 'master'
Rewrite home as a class-based view

See merge request klub-dev-ens/gestioCOF!369
2019-10-06 13:47:07 +02:00
Ludovic Stephan fba6b592f5 Merge branch 'kerl/fix_revente_crash' into 'master'
Fix crash on /bda/revente/<id>/manage

Closes #228

See merge request klub-dev-ens/gestioCOF!370
2019-10-06 12:01:53 +02:00
Martin Pépin db158ad312
Update changelog 2019-10-06 11:52:32 +02:00
Martin Pépin 8c9de4303b
Add a testcase for issue #228 2019-10-06 10:57:15 +02:00
Martin Pépin 9661751df2
Fix crash on /bda/revente/<id>/manage
`annotate_paid` method is a method of the Participant object manager,
not the Participant class itself
2019-10-06 10:32:18 +02:00
Martin Pépin d1c9d27a65
Rewrite home as a class-based view 2019-10-06 00:28:32 +02:00
Martin Pépin 838bf325ba Remove useless / unused dependencies
- unicodecsv is useless in py3
- autoslug is not used anywhere
- wheels comes with any correctly configured virtualenv
2019-10-05 17:38:31 +02:00
Ludovic Stephan b99fd03df2 Merge branch 'kerl/403_vs_404' into 'master'
Replace some 403 by 404 to avoid trigramme leaking

Closes #224

See merge request klub-dev-ens/gestioCOF!364
2019-10-05 16:01:46 +02:00
Martin Pépin d37c41e99f kfet/test_views: more eloquent test names 2019-10-05 13:48:29 +02:00
Martin Pépin a4ecd344d0 Update CHANGELOG 2019-10-05 11:28:59 +02:00
Martin Pépin e0285607a0
Fix tests according to issue #224 2019-10-05 02:25:05 +02:00
Martin Pépin 96adadce5e
Replace some 403 by 404 to avoid trigramme leaking
Fixes #224
2019-10-05 01:25:36 +02:00
Robin Champenois e8a9e808f5 Merge branch 'Aufinal/charte_bda' into 'master'
Ajoute un popup de charte BdA à l'inscription aux tirages

Closes #225

See merge request klub-dev-ens/gestioCOF!363
2019-09-26 21:00:33 +02:00
Ludovic Stephan 966cf6ce15 On hook le popup à form.submit() 2019-09-26 20:30:04 +02:00
Ludovic Stephan 6406b493a2 Add background opacity 2019-09-26 20:04:07 +02:00
Ludovic Stephan 0701213225 Copy jconfirm to shared 2019-09-18 20:44:35 +02:00
Ludovic Stephan 411e66b13c Style charte popup 2019-09-18 19:36:14 +02:00
Ludovic Stephan 5db7eef1d7 Add charte popup and functionality 2019-09-18 19:36:02 +02:00
Ludovic Stephan 0a1b20dd4e Add accepte_charte field to Participant model 2019-09-18 19:34:56 +02:00
Ludovic Stephan 92ebf0d233 Merge branch 'Kerl/registration_less_email_errors' into 'master'
Erreurs de mails lors de l'inscription d'un nouveau membre

Closes #173

See merge request klub-dev-ens/gestioCOF!348
2019-06-17 22:31:43 +02:00
Basile Clement 405f95a43b Early return if there is no email 2019-06-17 22:17:34 +02:00
Martin Pépin ab89002cfc Clearer error message 2019-06-17 22:17:34 +02:00
Martin Pépin c319780ab5 CHANGELOG: less email errors during registration 2019-06-17 22:17:34 +02:00
Martin Pépin 9f23f85b87 Handle errors when sending welcome emails during member registraton 2019-06-17 22:17:02 +02:00
Ludovic Stephan 1cf333f0fc Merge branch 'Aufinal/readonly_user' into 'master'
Désactive la modification des comptes COF sur l'interface K-Fêt

See merge request klub-dev-ens/gestioCOF!360
2019-06-17 22:15:57 +02:00
Ludovic Stephan bf372a1ce2 CHANGELOG 2019-06-17 22:03:11 +02:00
Ludovic Stephan 96430d852c Do not repeat default argument 2019-06-17 22:02:12 +02:00
Ludovic Stephan fc8c8fdf29 Montre les infos à tout le monde 2019-06-17 22:02:11 +02:00
Ludovic Stephan 8d30c5c7e5 Fix tests 2019-06-17 22:02:11 +02:00
Ludovic Stephan fb56293273 Supprime un formulaire inutilisé 2019-06-17 22:02:11 +02:00
Ludovic Stephan baa3826a42 Change le formulaire de account_update
On fait un formulaire d'info non-éditable pour pas que les gens mettent des noms troll
2019-06-17 22:02:11 +02:00
Ludovic Stephan 4598abc721 Merge branch 'Aufinal/paid_attributions' into 'master'
Déplace le champ `paid` des participants aux attributions

See merge request klub-dev-ens/gestioCOF!361
2019-06-17 21:59:01 +02:00
Ludovic Stephan d7d0daea0d Commentaire dans la fonction 2019-06-17 21:40:32 +02:00
Ludovic Stephan 46e7305953 Meilleur décorateur 2019-06-17 21:38:49 +02:00
Ludovic Stephan 4f15b820a5 Use manager from queryset 2019-06-17 21:36:09 +02:00
Ludovic Stephan edd92beadf Add logging call 2019-06-17 21:21:12 +02:00
Ludovic Stephan 20bb9fe54b Remove debug log 2019-06-17 21:13:12 +02:00
Ludovic Stephan ba5aa6da5f Remove useless local variable 2019-06-17 21:12:03 +02:00
Ludovic Stephan b11e35616c Changelog 2019-06-08 15:33:47 +02:00
Ludovic Stephan fa98bb34bd Adapte l'interface admin 2019-06-08 15:33:09 +02:00
Ludovic Stephan a67446048e Réécrit des vues pour la nouvelle fonctionnalité
On en profite pour rajouter des Mixins pour les perms buro/cof
2019-06-08 15:33:09 +02:00
Ludovic Stephan b37e7c4c41 Migrations 2019-06-08 15:33:09 +02:00
Ludovic Stephan 29111059f9 Rajoute un manager à Participant
On rajoute un manager qui annote les querysets avec si le participant a payé ou non
2019-06-08 15:33:09 +02:00
Ludovic Stephan 9776a18e4c Déplace les champs paid et paymenttype 2019-06-08 15:33:09 +02:00
Martin Pepin 7f1adf7c4e Merge branch 'Aufinal/can-delete-stuff' into 'master'
Délétions d'objets K-Fêt

See merge request klub-dev-ens/gestioCOF!359
2019-06-03 23:06:06 +02:00
Ludovic Stephan 56bc281b30 Utilise >= gnagnagna 2019-06-03 23:00:10 +02:00
Ludovic Stephan c4948be1f7 Use http_methods_allowed attribute 2019-06-03 22:59:43 +02:00
Ludovic Stephan f3dbb72f69 Consistency for on_delete attributes 2019-06-03 22:43:47 +02:00
Ludovic Stephan 173affd8eb Fix another 2.2 deprecation 2019-06-03 20:42:21 +02:00
Ludovic Stephan 51fe9cc9f8 Changelog 2019-05-29 18:31:03 +02:00
Ludovic Stephan d4be8b426e Tests pour la suppression d'articles 2019-05-29 18:29:15 +02:00
Ludovic Stephan 65dd7e5fa3 Suppression d'article
On fait pareil que précédemment pour les articles, en rajoutant une vie
de délétion + de quoi afficher qu'un article a été supprimé.
N.B. : le formatage automatique de VSCode fait plein de changements,
donc pourquoi pas les garder.
2019-05-29 18:29:15 +02:00
Ludovic Stephan 123e2b84df Rename view to fit conventions 2019-05-29 18:29:15 +02:00
Ludovic Stephan f12370a6cd Tests (!!) 2019-05-29 18:29:15 +02:00
Ludovic Stephan 52521e89a6 Add some restrictions on deletion 2019-05-29 18:29:15 +02:00
Ludovic Stephan 08ac0ac890 Vues de suppression
On rajoute un bouton de suppression d'un compte utilisable avec la perm
`kfet.delete_account`, avec message de vérif. On en profite pour
cleanup un peu le css de `jconfirm`.
2019-05-29 18:29:15 +02:00
Ludovic Stephan 63fff6ca7c Setup deleted account
Pour pouvoir supprimer un compte, on crée un compte dummy qui a pour but
de recevoir les objets non supprimables (caisses, transferts/opérations
pour statistiques, etc.). Lors de la délétion d'un compte, tout est
transféré sur le dummy, qui est créé via migration.
2019-05-29 18:29:15 +02:00
Ludovic Stephan 85b1e974ff Change deletion behaviour
Tous les `on_delete` étaient mis à PROTECT, ce qui faisait qu'on ne
pouvait rien supprimer... On les met à CASCADE pour tous les modèles
secondaires (`AccountNegative`, `CheckoutStatement`, `InventoryArticle`,
`SupplierArticle`, `Order` et `OrderArticle`) et pour les inventaires
créés à partir d'une commande.

Pour les modèles qui demandent une validation, et pour les `Operation`s,
on met à NULL le compte ou l'article associé (cela ne change pas le
total d'une opération, qui est la partie importante à garder).
2019-05-29 18:29:15 +02:00
Ludovic Stephan 3a5eceba83 Delete unused models
Certains modèles n'étaient pas utilisés dans le code, on en profite pour
les virer.
2019-05-29 18:29:15 +02:00
Ludovic Stephan 018865967d Merge branch 'Aufinal/django2-urls' into 'master'
Passage à Django2

See merge request klub-dev-ens/gestioCOF!358
2019-05-29 17:13:25 +02:00
Ludovic Stephan 8dd003f81f Changelog 2019-05-24 09:36:14 +02:00
Ludovic Stephan f03e708280 Tiens, une migration Wagtail 2019-05-23 15:21:40 +02:00
Ludovic Stephan 198658f5f9 Misc fixes
- on vire un commentaire obsolète, et on en remet un à jour
- un peu de doc sur les converters
2019-05-21 15:30:51 +02:00
Ludovic Stephan 03c74a7940 Misc fixes 2019-04-17 20:50:49 +02:00
Ludovic Stephan 7717d1ed34 Backwards-incompatible changes : queryset in filter 2019-04-17 18:33:43 +02:00
Ludovic Stephan 72560397a2 Backwards-incompatible changes : renderer argument 2019-04-17 18:27:14 +02:00
Ludovic Stephan 413a9cddb1 Backwards-incompatible changes : manytomany set 2019-04-17 18:21:59 +02:00
Ludovic Stephan 4064218010 Forgot one import 2019-04-17 18:21:15 +02:00
Ludovic Stephan a2fcc05672 Test fix : freeze psycopg2 version 2019-04-12 18:30:03 +02:00
Ludovic Stephan 8fc6f96324 Misc urlconf files 2019-04-12 17:07:03 +02:00
Ludovic Stephan 271732f40d K-Fêt urlconf file + converter 2019-04-12 17:06:53 +02:00
Ludovic Stephan 019acb90ac Global urlconf file 2019-04-12 17:06:43 +02:00
Ludovic Stephan 759b6d9489 Update settings ; remove debug_panel 2019-04-12 17:04:33 +02:00
Ludovic Stephan f32e4a9b0d Fix error 2019-04-12 17:03:42 +02:00
Ludovic Stephan 209cb1fbe5 Update requirements to Django 2.2 2019-04-12 17:03:20 +02:00
Ludovic Stephan ceff3ed6c9 Merge branch 'Aufinal/django2-login' into 'master'
Préparation Django2 : vues de login/logout

See merge request klub-dev-ens/gestioCOF!357
2019-04-01 22:25:07 +02:00
Ludovic Stephan 2c49b25d59 Cleaner error code management 2019-03-25 23:30:55 +01:00
Ludovic Stephan 5f963d5451 Use get_user_model 2019-03-25 23:22:06 +01:00
Ludovic Stephan a1ead1bfc8 Préparation Django2 : vues de login/logout
À partir de Django 2.1, les vues de login et logout sont class-based
uniquement. On passe donc à django-cas-ng 2.6 pour harmoniser.
On cleanup un peu le processus de login avec une classe un peu propre +
un vrai formulaire/des vrais templates.
2019-03-25 23:05:47 +01:00
Basile Clement cef75e56d7 Merge branch 'Aufinal/django2-reverse-import' into 'master'
Préparation Django2 :  fix des imports

See merge request klub-dev-ens/gestioCOF!356
2019-03-25 20:19:51 +01:00
Ludovic Stephan 8bfb8029b2 Changelog 2019-03-19 18:00:33 +01:00
Ludovic Stephan fd0f387dbc Merge branch 'Kerl/DJANGO_NO_DDT' into 'master'
Une variable d'environement pour désactiver la django debug toolbar

See merge request klub-dev-ens/gestioCOF!353
2019-03-19 17:59:39 +01:00
Ludovic Stephan 676239ad24 Remove urlresolvers mentions in packages
On bump quelques versions, et on vire `django_debug_panel`
2019-03-19 17:16:57 +01:00
Ludovic Stephan 5fd4cb5c78 Change django.core.urlresolvers imports
-- compatible with 1.11 --
2019-03-19 10:18:56 +01:00
Evarin 2b3a8760ff Mise à jour du Changelog 2019-02-18 22:50:35 +01:00
Robin Champenois 0eccfcf886 Merge branch 'Aufinal/webfonts' into 'master'
Sert les polices en local

See merge request klub-dev-ens/gestioCOF!354
2019-02-18 22:40:10 +01:00
Ludovic Stephan f90663bf97 Merge branch 'Evarin/Wagtail2' into 'master'
Migration vers Wagtail 2.3 et Wagtail-modeltranslation 0.9

See merge request klub-dev-ens/gestioCOF!349
2019-02-18 22:16:45 +01:00
Evarin 1043e5725a Fix content-type fixtures K-Fêt 2019-02-18 21:54:26 +01:00
Ludovic Stephan 3bf0906697 Supprime une police inutilisée 2019-02-13 17:05:31 +01:00
Ludovic Stephan 85642d00d8 Sert les polices en local 2019-02-13 16:54:42 +01:00
Martin Pépin fabb30cec2 add a DJANGO_NO_DDT env variable to disable ddt 2019-02-12 17:55:23 +01:00
Evarin 641bdd9464 Plus besoin de ça 2019-02-11 21:26:16 +01:00
Evarin 4f6579c3d1 Fixtures à jour pour Wagtail2 et wagtail-translation 0.9 2019-02-11 21:10:11 +01:00
Ludovic Stephan 1c45dd833d Black + isort 2019-02-09 15:16:40 +01:00
Ludovic Stephan f3117c9e69 Merge branch 'Elarnon/petitscours_edit' into 'master'
Quelques améliorations d'ergonomie sur les petits cours

See merge request klub-dev-ens/gestioCOF!350
2019-02-09 13:58:29 +01:00
Basile Clement fc72425c05 Add note to changelog 2019-02-09 13:47:18 +01:00
Basile Clement f74277af66 [petitscours] Ajoute un lien vers la liste des demandes
Ce patch ajoute un lien permettant de retourner sur la liste des
demandes à traiter depuis les pages de détail et de traitement d'une
demande.  Idéalement, on voudrait plutôt une espèce de fil d'ariane.
2019-02-09 13:46:42 +01:00
Basile Clement f64c7a6e69 [petitcours] Ajoute un lien pour modifier une demande
Ce patch ajoute un lien bidirectionnel entre la page d'affichage d'un
petit cours pour le Burô et l'administration générale.  Plus
précisément,

 - Un lien est ajouté sur la page du petit cours, ainsi que sur la page
   de traitement, vers l'administration générale

 - La fonctionalité "Voir sur le site" de Django est utilisée pour
   renvoyer sur la page de la demande.  Si des modifications sont
   apportées, il faut choisir "Enregistrer et continuer les
   modifications", puis cliquer sur "Voir sur le site".

Le workflow n'est pas forcément optimal, mais permet au COF d'accéder
facilement à la demande si un traitement manuel ou complexe est
nécessaire - et de facilement revenir à la vue de traitement.
2019-02-09 13:46:42 +01:00
Ludovic Stephan 73508c0251 Merge branch 'Kerl/localhost8000' into 'master'
Le vrai nom du site en dev c'est `localhost:8000` et pas `localhost`

See merge request klub-dev-ens/gestioCOF!351
2019-02-09 13:45:42 +01:00
Martin Pépin 2c4ba3258d the actual (dev) domain name is localhost:8000 2019-02-09 13:39:46 +01:00
Evarin 0f1e05acdd Migration vers Wagtail 2.3 et Wagtail-modeltranslation 0.9
Toutes les pages Wagtail doivent désormais être traduites
Suppression du modèle COFUtilPage devenu inutile
Réinitialisation des migrations de Wagtail à cause des changements de ModelTranslation
2019-02-04 22:56:48 +01:00
Ludovic Stephan 86dbdbd2b6 Merge branch 'Kerl/del_old_view' into 'master'
Supprime la vue bda.views.descriptions_spectacles

Closes #154

See merge request klub-dev-ens/gestioCOF!347
2019-02-04 21:43:14 +01:00
Martin Pépin c134e1ae0f remove unused template 2019-02-04 21:37:45 +01:00
Martin Pépin abf05131d5 CHANGELOG: remove old bda view 2019-02-04 21:06:24 +01:00
Martin Pépin 869e634adb remove bda.views.descriptions_spectacles
This view has been unused for a long time
It has been replace by bda.views.catalogue
2019-02-04 20:58:53 +01:00
Aurélien Delobelle be155f2f2d Merge branch 'Kerl/unique_login_clipper' into 'master'
Force l'unicité des clippers

See merge request klub-dev-ens/gestioCOF!346
2019-01-15 20:18:33 +01:00
Martin Pépin 68cead600f CHANGELOG: clipper uniqueness 2019-01-14 23:12:29 +01:00
Martin Pépin d85eeb5801 Enforce clipper uniqueness 2019-01-14 22:46:16 +01:00
Martin Pépin 2140a59777 add missing entries in the CHANGELOG file 2019-01-14 21:56:17 +01:00
Martin Pepin 512868ee14 Merge branch 'evarin/site-cof' into 'master'
Nouveau site du COF

See merge request klub-dev-ens/gestioCOF!247
2019-01-14 21:23:10 +01:00
Martin Pepin d7d819dde0 Merge branch 'Aufinal/revente-cof-only' into 'master'
BdA-Revente n'est accessible qu'aux adhérents du COF

See merge request klub-dev-ens/gestioCOF!342
2019-01-14 20:30:33 +01:00
Martin Pepin eb83c58f05 Merge branch 'Aufinal/prettify-revente' into 'master'
Rend BdA-Revente un peu plus joli

See merge request klub-dev-ens/gestioCOF!338
2019-01-13 14:29:48 +01:00
Martin Pepin f883082814 Merge branch 'aureplop/lint-migrations' into 'master'
style: black

See merge request klub-dev-ens/gestioCOF!343
2019-01-13 14:05:32 +01:00
Aurélien Delobelle b65d37b141 style: black 2019-01-13 13:56:03 +01:00
Ludovic Stephan aa498e0261 BdA-Revente n'est accessible qu'au COF 2019-01-10 10:31:58 +01:00
Martin Pépin 670fda3c9c migrations nécessaires en prod ?!? wtf 2019-01-07 23:30:56 +01:00
Martin Pépin dd5d773405 add CHANGELOG 2019-01-07 23:11:57 +01:00
Ludovic Stephan d08cec06d3 Merge branch 'master' into Aufinal/prettify-revente 2019-01-07 22:55:48 +01:00
Ludovic Stephan 1f3e65fa68 Fix javascript code 2019-01-07 22:52:18 +01:00
Ludovic Stephan 55c1d7f02c Refactor templates 2019-01-07 22:51:02 +01:00
Ludovic Stephan 71cae7f5ca Remove dead code 2019-01-07 22:50:30 +01:00
Ludovic Stephan 445745ee15 Merge branch 'master' into Aufinal/prettify-revente
Merge remote-tracking branch 'origin/master' into Aufinal/prettify-revente
2019-01-07 22:34:28 +01:00
Aurélien Delobelle 6e51ca749d Merge branch 'Kerl/syncmails' into 'master'
On oublie l'argument de syncmail dans `prepare_django.sh`

See merge request klub-dev-ens/gestioCOF!340
2019-01-07 22:33:58 +01:00
Martin Pépin babb458aa4 fix syncmail invocation in prepare_django.sh 2019-01-07 22:10:58 +01:00
Martin Pepin 288e3f15b6 Merge branch 'Aufinal/djdt_admin' into 'master'
Remove Django-debug-toolbar in admin interface

See merge request klub-dev-ens/gestioCOF!339
2019-01-07 20:58:55 +01:00
Ludovic Stephan bb23c45fad Remove Django-debug-toolbar in admin interface
La présence de DJDT sur l'interface admin multiplie par 100 environ
(sans exagération) les temps de chargement des pages, cause des 503, et
a une utilité au plus limitée. Cette MR vire donc l'addon de l'interface
admin.
2019-01-07 17:38:55 +01:00
Ludovic Stephan 47c02d72af Réorganisation de bda/forms
Suppression de code mort, tri des formulaires
Remove bootstrap forms loading
2019-01-07 16:43:46 +01:00
Ludovic Stephan ae0abb5cb3 Better JS for tables 2019-01-07 16:28:52 +01:00
Ludovic Stephan f66a54bb73 Prettify revente/manage 2019-01-07 16:24:30 +01:00
Ludovic Stephan 010ce0df3e Bugfixes : add staticfiles and typo 2019-01-07 15:27:41 +01:00
Ludovic Stephan 519ef9dc20 Fix concurrency issues
Creating form fields in the class and modifiying them dynamically can
cause concurrency issues because the form class is shared between tabs.
2019-01-07 14:59:20 +01:00
Ludovic Stephan a30955fb75 HTML corrections ; stupidtable injection 2019-01-07 14:58:31 +01:00
Ludovic Stephan 31223aaed9 Merge branch 'aureplop/kfet-tests_deterministic' into 'master'
kfet.tests -- Deterministic responses for cancel_operation

See merge request klub-dev-ens/gestioCOF!337
2019-01-07 14:12:41 +01:00
Aurélien Delobelle 5d14fef032 kfet.tests -- More isolated tests for kfet.open 2019-01-06 13:49:07 +01:00
Aurélien Delobelle 7ca0144004 kfet.tests -- Deterministic responses for cancel_operation 2019-01-06 13:49:07 +01:00
Ludovic Stephan 7c1d1df1a9 Merge branch 'aureplop/bda-tests_misc-views' into 'master'
bda.tests -- Add some tests for non-reventes views

See merge request klub-dev-ens/gestioCOF!326
2019-01-06 13:12:09 +01:00
Robin Champenois a6bf1fc16a Merge branch 'aureplop/site-cof' into 'evarin/site-cof'
evarin/site-cof: style -- black + isort

See merge request klub-dev-ens/gestioCOF!336
2019-01-06 12:02:31 +01:00
Aurélien Delobelle 84c88dfd5e Merge branch 'master' into aureplop/site-cof 2019-01-06 00:56:21 +01:00
Aurélien Delobelle 39eaf4b109 style -- black + isort 2019-01-06 00:37:08 +01:00
Evarin 376cc96343 Clean, lint and fix little things 2019-01-06 00:17:57 +01:00
Martin Pepin e0e75b53bb Merge branch 'aureplop/ci_parallel' into 'master'
core.ci -- Run tests in parallel

See merge request klub-dev-ens/gestioCOF!327
2019-01-05 19:36:20 +01:00
Aurélien Delobelle aba0be7960 kfet.test -- Isolate kfet_open in testcases to avoid cache collisions 2019-01-05 19:19:58 +01:00
Martin Pepin a057776e1f Merge branch 'aureplop/linters-for-petitscours' into 'master'
style -- Linters for petitscours, fix isort

See merge request klub-dev-ens/gestioCOF!335
2019-01-05 19:16:28 +01:00
Martin Pepin e21154e869 Merge branch 'aureplop/kfet-tests_cancel_operations' into 'master'
kfet.tests -- Add tests for cancel_operations view + small things

See merge request klub-dev-ens/gestioCOF!315
2019-01-05 19:15:49 +01:00
Evarin f105225abf Commentaires dans le code 2019-01-05 18:50:53 +01:00
Aurélien Delobelle debbf265c4 style -- Linters for petitscours, fix isort 2019-01-05 18:19:26 +01:00
Aurélien Delobelle 8db55d792c core.ci -- Run tests in parallel
tblib displays traceback correctly with --parallel
2019-01-05 17:41:41 +01:00
Aurélien Delobelle 09e99ee3a3 bda.tests -- Add tests for descriptions_spectacles view 2019-01-05 15:28:11 +01:00
Aurélien Delobelle 3e38e48d7a bda.tests -- Add tests for tirage inscription view 2019-01-05 15:28:11 +01:00
Aurélien Delobelle 1664554083 bda.tests -- Add Spectacle factories 2019-01-05 15:28:11 +01:00
Aurélien Delobelle f8610d4ff1 bda.tests -- Split view tests and use shared test helpers 2019-01-05 15:28:11 +01:00
Aurélien Delobelle a71fbb0ed3 tests -- Ignore url entries without name 2019-01-05 15:28:05 +01:00
Aurélien Delobelle 57a2af285a bda -- Add name to bda url 2019-01-05 15:27:05 +01:00
Robin Champenois e6e4a13440 Merge branch 'Elarnon/master' into 'master'
Ajoute une option "Master" pour les petits cours

See merge request klub-dev-ens/gestioCOF!334
2018-12-21 11:12:08 +01:00
Basile Clement f9ddee60ed Ajoute une option "Master" pour les petits cours
À la demande du COF, car il y a des parents qui demandent et illes se
retrouvent à changer les demande "Autres" en "Licence 3" via l'admin
Django.  En pratique, il y aura sans doute très peu de profs qui
proposent des cours de Master (aussi appelé "le problème des gens qui
remplissent leurs compétences une seule fois en conscritude");  il
faudra donc tout de même laisser la possibilité au COF de changer
manuellement pour matcher avec "Licence 3", mais faisons une chose à la
fois.  On pourrait aussi harceler les gens pour qu'illes mettent à jour
leurs compétences en début d'année (c'est-à-dire mettre un bandeau
temporaire pour leur rappeler que si illes mettent à jour leurs
compétences ça augmente leur chance d'obtenir des cours).
2018-12-10 20:54:39 +01:00
Ludovic Stephan 66104e1137 Black 2018-12-08 10:41:46 +01:00
Ludovic Stephan 15ab316909 Merge branch 'master' into Aufinal/prettify-revente 2018-12-08 10:40:10 +01:00
Ludovic Stephan 6be42d57ca Prettify revente/shotgun 2018-12-07 17:35:53 +01:00
Ludovic Stephan 5c8164dd3b Prettify revente/tirages 2018-12-07 17:35:40 +01:00
Ludovic Stephan 625825cf3f Améliore TemplateLabelField
- Rajouter une option pour `option_template_name` et `context_object_name` dans la classe, et documente mieux. Répercute ces changements dans `InscriptionReventeForm`.
2018-12-07 17:33:17 +01:00
Ludovic Stephan dee2f4badc Merge branch 'Elarnon/petitscours_proposals_cleanup' into 'master'
[petitscours] Extrait la proposition de profs dans une méthode

See merge request klub-dev-ens/gestioCOF!332
2018-12-03 21:15:17 +01:00
Martin Pepin 633cd49094 Merge branch 'Elarnon/cofburo_required' into 'master'
Améliore l'ergonomie de `cof_required` et `buro_required`

See merge request klub-dev-ens/gestioCOF!333
2018-11-27 10:13:32 +01:00
Martin Pepin d58d08a4d4 Merge branch 'Elarnon/petitscours_app' into 'master'
Extrait les petits cours dans une application séparée

See merge request klub-dev-ens/gestioCOF!331
2018-11-26 23:00:56 +01:00
Basile Clement 5f9695ef8e isort 2018-11-25 18:32:04 +01:00
Basile Clement 2e08951d44 Améliore l'ergonomie de cof_required et buro_required
Ce patch rend les décorateurs `cof_required` et `buro_required` plus
agréables pour les utilisateurs; en particulier, ils ne font plus une
redirection sur la page de connexion si la condition n'est pas remplie.

Dans les deux cas :

 - Si l'utilisateur n'est pas connecté, il est renvoyé sur la page de
   connexion

 - Si l'utilisateur est connecté mais pas membre du COF/du Burô, une
   page d'erreur "403 Forbidden" est affichée.  Dans le cas de
   `cof_required` cette page demande à l'utilisateur de s'inscrire au
   COF; dans le cas `buro_required` elle indique simplement que la page
   est réservée au Burô.

gestioncof/
 * gestioncof/templates/buro-denied.html:  Ajouté.
 * decorators.py:

bda/
 * tests/test_views.py:
       Modifié pour correctement gérer le nouveau fonctionnement des
       décorateurs.
2018-11-25 18:11:23 +01:00
Basile Clement 2b8f81c94b [petitscours] Extrait la proposition de profs dans une méthode
Ce patch simplifie le code (dupliqué) de calcul des proposition de profs
pour une demande dans une méthode du modèle`Demande`, et l'utilise.  Il
s'agit d'un préparatif pour #208; ce code devra être réutilisé dans le
nouveau système.

J'en ai également profité pour nettoyer deux vues de `petitscours`,
`retraitement` et `demande_raw`, qui dupliquaient les vues `traitement`
et `demande`, en utilisant des arguments nommés.

petitscours/
 * models.py:
    Définition de `get_proposals` pour calculer les propositions de
    profs pour une demande.
 * views.py:
    Utilise `get_proposals` à la place du code copié-collé.  La fonction
    `_finalize_traitement` est maintenant responsable du calcul des
    `proposed_for` et `attribdata` à fournir aux templates.
 * urls.py:
    Passe directement les arguments aux vues plutôt que de faire deux
    fonctions séparées.
2018-11-25 17:05:55 +01:00
Basile Clement c960d97b67 Extrait les petits cours dans une application séparée
L'application `petitscours` reste assez fortement couplée à
`gestioncof`, et n'est pas (encore ?) faite pour être utilisée
séparément.

De façon similaire, et afin de minimiser de potentiels problèmes dûs à
des migrations, les modèles de l'application `petitscours` utilisent
`app_label = "gestioncof"` pour que Django les considère comme faisant
partie de l'application `"gestioncof"`.  Ils pourront être migrés dans
un second temps si cela s'avère nécessaire.

Les changements sont nombreux, mais assez simples: il s'agit
principalement de déplacer des fichiers et changer des imports.  J'ai
également profité de l'occasion pour réorganiser les templates afin de
les placer dans l'espace de nom "petitscours/".

cof/
 * settings/common.py: Add `petitscours` app
 * urls.py: Use `petitscours.urls`

petitscours/
 * __init__.py: Added.
 * tests/__init__.py: Added.
 * tests/utils.py: Added.
 * urls.py: Added.

gestioncof/
 * admin.py:
 * management/commands/loaddevdata.py:
 * models.py:
 * signals.py: Typo.
 * urls.py:
       Moved petitscours_patterns to petitscours.urls
 * petits_cours_forms.py:
       Moved to petitscours/forms.py
 * petits_cours_models.py:
       Moved to petitscours/models.py
 * petits_cours_views.py:
       Moved to petitscours/views.py
 * tests/utils.py:
 * tests/test_petitscours_views.py:
       Moved to petitscours/tests/test_petitscours_views.py
 * templates/base_title_petitscours.html:
       Moved to petitscours/templates/petitscours/base_title.html
 * templates/demande-petit-cours.html:
       Moved topetitscours/templates/petitscours/demande.html
 * templates/gestioncof/details_demande_petit_cours.html:
       Moved to petitscours/templates/petitscours/demande_detail.html
 * templates/petits_cours_demandes_list.html:
       Moved to petitscours/templates/petitscours/demande_list.html
 * templates/demande-petit-cours-raw.html:
       Moved to petitscours/templates/petitscours/demande_raw.html
 * templates/details_demande_petit_cours_infos.html:
       Moved to petitscours/templates/petitscours/details_demande_infos.html
 * templates/inscription-petit-cours.html:
       Moved to petitscours/templates/petitscours/inscription.html
 * templates/inscription-petit-cours-formset.html:
       Moved to petitscours/templates/petitscours/inscription_formset.html
 * templates/gestioncof/traitement_demande_petit_cours.html:
       Moved to petitscours/templates/petitscours/traitement_demande.html
 * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html:
       Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html
 * templates/gestioncof/traitement_demande_petit_cours_success.html:
       Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 13:25:16 +01:00
Ludovic Stephan d82c9baf20 Bump django-redis-cache version to 1.8.1
The `django-redis` package does not work with redis 3.0, and previous
versions of this package did not hardcode the version number.
2018-11-25 00:48:57 +01:00
Ludovic Stephan 4ae0b3c5f0 Merge branch 'aureplop/cof-tests_petitcours' into 'master'
petitcours.tests -- Add tests for some views

See merge request klub-dev-ens/gestioCOF!325
2018-11-25 00:41:51 +01:00
Basile Clement 4e34583e3f black 2018-11-25 00:23:43 +01:00
Basile Clement a2116bf290 Merge branch 'master' into aureplop/cof-tests_petitcours 2018-11-25 00:22:16 +01:00
Basile Clement d48cb3aaed petitcours.tests -- Deplace les tests dans leur propre fichier 2018-11-25 00:22:12 +01:00
Ludovic Stephan dbd017f680 Prettify revente/subscribe 2018-11-21 19:47:20 +01:00
Martin Pépin 712588af7d Merge branch 'master' into evarin/site-cof 2018-11-19 23:30:33 +01:00
Robin Champenois 6524f89ebd Merge branch 'Elarnon/bda_tirage_message_d_erreur_quand_il_y_a_une_erreur' into 'master'
Affiche un message d'erreur lors de l'enregistrement des voeux BDA

See merge request klub-dev-ens/gestioCOF!330
2018-11-12 23:22:38 +01:00
Basile Clement 7f2f25cb71 Isort 2018-11-12 23:04:37 +01:00
Basile Clement 042ce80b78 Run black 2018-11-12 22:52:20 +01:00
Basile Clement d7f4d32c92 Oh mon dieu! 2018-11-12 22:46:02 +01:00
Basile Clement b80927efa3 Message d'erreur qui ne parle pas de object Voeu 2018-11-12 22:44:09 +01:00
Basile Clement 511981e762 Simplifie la Logique 2018-11-12 22:22:49 +01:00
Basile Clement e09fa2b847 Affiche un message d'erreur lors de l'enregistrement des voeux BDA
Voir #203
2018-11-12 22:16:43 +01:00
Robin Champenois 39d82a573f Merge branch 'Elarnon/autoriser_la_suppression_dun_voeud_bda_qui_nexiste_pas' into 'master'
Permet la suppression d'un voeu ajouté mais non enregistré

See merge request klub-dev-ens/gestioCOF!329
2018-11-12 22:03:07 +01:00
Basile Clement 7124821f7c Permet la suppression d'un voeu ajouté mais non enregistré
Voir #203.  Cette solution est horrible, tout comme le code dans
lequel elle se trouve.  Déso pas déso.
2018-11-12 21:56:40 +01:00
Ludovic Stephan ba21de683b Merge branch 'Elarnon/cas_v3_v2' into 'master'
Fix CAS support in python-cas 1.3+

See merge request klub-dev-ens/gestioCOF!328
2018-11-12 21:09:14 +01:00
Basile Clement 00a1e79af6 Fix CAS support in python-cas 1.3+
cas.eleves.ens.fr has /serviceValidate, not /p3/serviceValidate, and is
thus *probably* a V2 CAS server.  python-cas was broken and using
/serviceValidate for V3 while it should have been /p3/serviceValidate,
see
c3ac4b6c76
2018-11-12 00:54:44 +01:00
Ludovic Stephan 95ba7798d7 Ignore VSCode files 2018-11-04 23:07:57 +01:00
Aurélien Delobelle 0fe63d3eae petitcours.tests -- Add tests for demande (raw) views 2018-10-27 22:29:55 +02:00
Aurélien Delobelle 3d27dc9a41 petitcours.tests -- Add tests for inscription and (re)traitement views 2018-10-27 19:18:58 +02:00
Aurélien Delobelle 1a5bbf32a4 petitcours.tests -- Add tests for demandes list and details views 2018-10-27 19:18:58 +02:00
Aurélien Delobelle 25dfe2f496 petitcours.tests -- Add PCAbility, PCDemande and PCSubject factories 2018-10-27 19:18:58 +02:00
Aurélien Delobelle 8be913cbf9 style -- black 2018-10-27 13:37:39 +02:00
Aurélien Delobelle 49a74e8e1e Merge branch 'Kerl/syncmails' into 'master'
Utilisation de la command `syncmails` du package custommail

See merge request cof-geek/gestioCOF!323
2018-10-21 14:22:59 +02:00
Aurélien Delobelle b69f1b6dbc kfet.tests -- Add tests for cancel_operations view 2018-10-21 13:07:44 +02:00
Aurélien Delobelle d7ca072af3 kfet.tests -- Add factories for many kfet models
- Article
- ArticleCategory
- Checkout
- CheckoutStatement
- Inventory
- InventoryArticle
- Operation
- OperationGroup
2018-10-21 12:50:09 +02:00
Aurélien Delobelle 928abc5a06 core -- Bump version django-djconfig to 0.8.0
Bump djcondig to last version.

Previously used version was failing on some updates, e.g:
kfet_config.set(cancel_duration=timedelta(minutes=15))
2018-10-21 12:50:09 +02:00
Aurélien Delobelle 0ba7100110 Merge branch 'aureplop/ci-move-services' into 'master'
core.ci -- Fix CI (postgres version) + speed up job "linters"

See merge request cof-geek/gestioCOF!324
2018-10-21 12:49:25 +02:00
Aurélien Delobelle b795a06b9c core.ci -- Use postgres version of production server 2018-10-21 12:33:58 +02:00
Aurélien Delobelle e478beee5c core.ci -- Narrow services to jobs that need them 2018-10-21 12:33:54 +02:00
Evarin 128a9e32c0 I18n des menus 2018-10-14 16:29:26 +02:00
Evarin 1e3850bb6b Nettoyage dates et calendrier 2018-10-14 15:50:55 +02:00
Martin Pepin 7f5a442bae Merge branch 'Aufinal/annul_reventes' into 'master'
Annulation des reventes

Closes #191

See merge request cof-geek/gestioCOF!297
2018-10-07 12:16:07 +02:00
Martin Pépin 9da9649a45 Use the syncmail command as defined in custommail 2018-10-07 00:55:54 +02:00
Martin Pépin 1f350d60dd Merge branch 'master' into Aufinal/annul_reventes 2018-10-07 00:34:36 +02:00
Aurélien Delobelle 26b19685b8 Merge branch 'Kerl/fix-pre-commit' into 'master'
Autre idée pour réparer le hook de pre-commit

See merge request cof-geek/gestioCOF!322
2018-10-07 00:09:13 +02:00
Martin Pépin 2c0ab1e55e use xargs to prevent globbing in pre-commit.sh 2018-10-07 00:06:51 +02:00
Martin Pepin b86f3113ea Merge branch 'aureplop/bda-test_silence' into 'master'
bda.tests -- Silence syncmails in setup

See merge request cof-geek/gestioCOF!320
2018-10-06 17:47:54 +02:00
Aurélien Delobelle aac9b41442 bda.tests -- Silence syncmails in setup 2018-10-06 17:39:52 +02:00
Martin Pepin ec287c8a3b Merge branch 'aureplop/linters' into 'master'
core -- Add and apply black, flake8, isort to CI and pre-commit

Closes #200

See merge request cof-geek/gestioCOF!317
2018-10-06 16:10:33 +02:00
Aurélien Delobelle fc4b852bde Merge branch 'Kerl/linters' into 'aureplop/linters'
Script de pre-commit plus robuste

See merge request cof-geek/gestioCOF!319
2018-10-06 16:04:36 +02:00
Martin Pépin 9bc3355a21 pre-commit hook: fix shellcheck's SC2086 & SC2181 2018-10-06 15:50:49 +02:00
Martin Pépin 61fbf0bc80 typo 2018-10-06 15:45:32 +02:00
Aurélien Delobelle 402b544393 core -- Fix flake8 errors 2018-10-06 13:15:33 +02:00
Aurélien Delobelle fdd2b35289 core -- Apply black + isort to all files 2018-10-06 13:15:33 +02:00
Aurélien Delobelle 104e71dcf6 core -- Add black,isort,flake8 to CI and pre-commit hook
On CI:
- black and isort in check mode must pass.
- flake8 only prints errors
  WIP: make it also failed.

On pre-commit:
- black and isort will format staged files, if installed on path.
- flake8 prints its output if necessary.
2018-10-06 12:53:50 +02:00
Aurélien Delobelle b23810917d core -- Remove not working cache of py installed packages...
... and use env var for pip install location.
2018-10-06 11:54:18 +02:00
Martin Pepin b39b6d6bb3 Merge branch 'aureplop/cof-tests_registration' into 'master'
cof -- Add tests for registration views

See merge request cof-geek/gestioCOF!287
2018-10-06 11:25:44 +02:00
Aurélien Delobelle dda803b7d5 Merge branch 'aureplop/ci-disable-coverage-in-gitlab' into 'master'
core -- Disable coverage in GitLab CI

See merge request cof-geek/gestioCOF!318
2018-10-06 10:14:35 +02:00
Aurélien Delobelle cc4e3223b6 core -- Disable coverage in GitLab CI 2018-10-05 23:36:59 +02:00
Martin Pepin 39abfceb76 Merge branch 'aureplop/kfet-tests_perform_operations' into 'master'
kfet.tests -- Add tests for perform_operations view + small things

See merge request cof-geek/gestioCOF!313
2018-10-04 23:59:30 +02:00
Martin Pepin 56d979bb47 Merge branch 'aureplop/add-codecoverage-to-ci' into 'master'
core -- Add code coverage to CI

See merge request cof-geek/gestioCOF!314
2018-10-04 19:06:57 +02:00
Aurélien Delobelle 6c5b7ed5cc core -- Update CI badge for current GitLab version 2018-10-04 11:36:45 +02:00
Aurélien Delobelle 7e55bf0cb1 core -- Add code coverage to CI 2018-10-04 11:36:45 +02:00
Aurélien Delobelle 507a59c914 kfet.tests -- Add tests for perform_operations view 2018-10-04 11:25:10 +02:00
Aurélien Delobelle 22011faba9 kfet -- Init KFetConfig, even without request, for easy testing 2018-10-04 11:25:10 +02:00
Aurélien Delobelle 0f688a8f1c kfet -- Stack errors of KPsulOperationForm
Delete an error never raised, and avoid duplicate messages.
2018-10-04 11:25:10 +02:00
Aurélien Delobelle 93d3c124fd kfet -- Add fixme related to available checkouts in K-Psul 2018-10-04 11:25:10 +02:00
Martin Pepin ec29e6ef5c Merge branch 'aureplop/kfet-tests_remove-unused-views' into 'master'
kfet -- Remove unused view

See merge request cof-geek/gestioCOF!312
2018-10-03 23:07:04 +02:00
Aurélien Delobelle 79c26c9dd6 kfet -- Remove unused view 2018-10-01 15:37:41 +02:00
Aurélien Delobelle 44af796e73 Merge branch 'Kerl/tests' into 'master'
Quelques tests pour les vues BdA

See merge request cof-geek/gestioCOF!280
2018-09-30 13:39:22 +02:00
Aurélien Delobelle 6858df02be bda.tests -- Use urllib urlencode 2018-09-30 13:22:22 +02:00
Aurélien Delobelle 3eb939928f Merge branch 'master' into Kerl/tests 2018-09-30 13:08:58 +02:00
Aurélien Delobelle 10f4bd02d5 Merge branch 'master' into aureplop/cof-tests_registration 2018-09-30 12:57:35 +02:00
Aurélien Delobelle 064c23902b cof.tests -- Address flake8 concerns 2018-09-30 12:56:58 +02:00
Aurélien Delobelle 44e5387f15 cof.tests -- Really check initial of built form 2018-09-30 12:56:36 +02:00
Martin Pépin f297a1a0cf update hardcoded Mega views for 2018… 2018-09-09 07:20:18 +02:00
Martin Pépin a750c62baf New year, new promotion: 2018 2018-09-02 23:27:21 +02:00
Martin Pépin 19c51c6a3a Merge branch 'Kerl/unernestaparis' 2018-09-02 23:27:05 +02:00
Martin Pépin 327ef210db make unernestaparis visible in forms 2018-09-02 23:26:18 +02:00
Martin Pépin 73cf39baa8 missing migration 2018-09-02 23:25:58 +02:00
Theo Delemazure 91393dcea7 Update models.py 2018-09-02 20:34:09 +02:00
Martin Pepin a8bfedb28b Merge branch 'Kerl/ci-py35' into 'master'
Utilise python 3.5 dans l'intégration continue

See merge request cof-geek/gestioCOF!306
2018-09-02 18:24:24 +02:00
Martin Pepin 92ca838601 Merge branch 'Kerl/changeemail' into 'master'
Les membres peuvent changer leur adresse email

See merge request cof-geek/gestioCOF!305
2018-09-02 18:24:03 +02:00
Martin Pépin 66184fbee6 CI: set python version to 3.5 2018-08-05 18:34:05 +02:00
Martin Pépin 898a354c2d Members can change their registration email 2018-08-05 18:11:10 +02:00
Evarin 954a6fdb53 Wagtail requirement update 2018-06-30 15:52:58 +02:00
Evarin 6d6ba70bd7 CSS++ 2018-06-30 15:38:12 +02:00
Ludovic Stephan e515a55956 Merge branch 'aureplop/194-kfet_edit-account' into 'master'
kfet -- As a kfet staff, remove the requirement to…

Closes #194

See merge request cof-geek/gestioCOF!303
2018-06-16 17:13:17 +02:00
Aurélien Delobelle 645c01ebd1 kfet -- As a kfet staff, remove the requirement to…
provide a password in the edit view of an account.
2018-06-16 12:33:51 +02:00
Martin Pépin 68e71317cb Hotfix: broken urls for mailing lists 2018-06-01 17:08:24 +02:00
Martin Pepin 96416bffea Merge branch 'Aufinal/fix-revente-transfer' into 'master'
Fix le transfert des reventes

See merge request cof-geek/gestioCOF!302
2018-05-30 10:19:16 +02:00
Ludovic Stephan 12ae10f2c4 Fix le transfert des reventes
Il y avait une typo qui causait une erreur quand on voulait transférer
une revente.
2018-05-30 10:13:37 +02:00
Martin Pepin af817534e4 Merge branch 'Aufinal/autocomplete-js' into 'master'
Fix autocomplete K-Psul

See merge request cof-geek/gestioCOF!301
2018-05-29 15:52:43 +02:00
Ludovic Stephan 837b48af36 Fix js path for autocomplete 2018-05-29 14:39:29 +02:00
Martin Pepin c7cdf29129 Merge branch 'Kerl/issue193' into 'master'
Issue193 : lien cassé sur K-Psul

Closes #193

See merge request cof-geek/gestioCOF!300
2018-05-28 13:55:13 +02:00
Martin Pépin 7e9df3fc72 hotfix: wrong url name 2018-05-28 00:09:30 +02:00
Martin Pépin b0301f0304 fix slugurl name error 2018-05-28 00:07:34 +02:00
Martin Pépin 2a9125ffaa resolve migration conflict 2018-05-24 21:23:46 +02:00
Martin Pépin 483f192af3 Merge branch 'Production' 2018-05-24 21:23:01 +02:00
Martin Pepin 20cc2e7345 Merge branch 'aureplop/captcha' into 'master'
Upgrade to reCAPTCHA v2

Closes #192

See merge request cof-geek/gestioCOF!299
2018-05-14 17:55:37 +02:00
Aurélien Delobelle ece9a54df3 Upgrade to reCAPTCHA v2
reCAPTCHA v1 has been shut down since March 2018.

We now uses reCAPTCHA v2:
- user must check a simple checkbox (No CAPTCHA),
- eventually he must validate a challenge.

Moving keys settings allows to use the captcha for development.

Fixes #192.
2018-05-14 13:22:59 +02:00
Evarin 6d72644ee3 Clean code up 2018-04-28 15:59:49 +02:00
Martin Pépin e21666a112 Fix old-style urls (registration) 2018-04-16 16:34:34 +02:00
Ludovic Stephan f85d51e3bd Merge branch 'Kerl/drop_py2_compat' into 'master'
Suppression des derniers vestiges de la compatibilité python 2

Closes #103

See merge request cof-geek/gestioCOF!278
2018-04-16 15:21:00 +02:00
Martin Pepin 2818e43b2e Merge branch 'Kerl/readme' into 'master'
Recommande l'installation manuelle dans le README

See merge request cof-geek/gestioCOF!298
2018-04-16 14:24:55 +02:00
Martin Pépin ea737dab29 Merge branch 'Kerl/drop_py2_compat' of git.eleves.ens.fr:cof-geek/gestioCOF into Kerl/drop_py2_compat 2018-04-16 14:18:21 +02:00
Martin Pépin a73736bf41 Merge branch 'master' into Kerl/drop_py2_compat 2018-04-16 14:12:36 +02:00
Martin Pépin 2faa8d6b65 README: typo, some links, … 2018-04-14 23:11:14 +02:00
Martin Pépin 769f4fc7b8 README: mention the test database and unit tests 2018-04-14 14:15:29 +02:00
Martin Pépin dd9a81d891 Update install instructions 2018-04-14 14:00:37 +02:00
Ludovic Stephan fc37c5a8a0 Annulation des reventes
- On peut annuler des reventes à tout point du processus
- Le formulaire d'annulation donne plus d'informations
2018-04-13 10:54:45 +02:00
Martin Pépin 6168045c3a bda: swap double/autoquit descriptions 2018-04-09 22:43:25 +02:00
Martin Pepin 6c983e16e5 Merge branch 'Roussille/bda' into 'master'
autorisation des requêtes cross-domain

See merge request cof-geek/gestioCOF!253
2018-04-08 22:43:30 +02:00
Martin Pépin a7cd1e04cd prefer CORS_ORIGIN_WHITELIST to CORS_ORIGIN_REGEX_WHITELIST 2018-04-08 22:33:19 +02:00
Martin Pépin 09cfcc476a Bump django-cors-headers 2018-04-08 22:32:59 +02:00
Martin Pépin 556c354f8a Merge branch 'master' into Roussille/bda 2018-04-08 22:30:48 +02:00
Martin Pepin 25f4c64835 Merge branch 'Aufinal/bda_fixes' into 'master'
Fix pour BdA-Revente

See merge request cof-geek/gestioCOF!263
2018-04-07 14:55:33 +02:00
Martin Pépin 87a6722143 Merge branch 'master' into Aufinal/bda_fixes 2018-04-07 14:46:07 +02:00
Martin Pépin 568a58c52a Merge branch 'qwann/separate_list' 2018-04-07 14:25:04 +02:00
Martin Pépin 53a4c78903 Remove duplicate line 2018-04-07 14:21:07 +02:00
Martin Pépin 158b19778b also sort the unsold table 2018-04-07 14:20:41 +02:00
Martin Pépin 6d0ec6de43 Merge branch 'master' into qwann/separate_list 2018-04-07 14:09:48 +02:00
Martin Pepin 9d7486585b Merge branch 'aureplop/kfet-sticky_thead' into 'master'
kfet -- Tables are sortable

Closes #171

See merge request cof-geek/gestioCOF!277
2018-04-07 13:47:55 +02:00
Martin Pépin 71b4e6253d Merge branch 'master' into aureplop/cof-tests_calendar 2018-04-07 13:42:19 +02:00
Martin Pépin fba105fdd1 Merge branch 'master' into aureplop/kfet-sticky_thead 2018-04-07 13:32:20 +02:00
Martin Pépin 7512454825 Merge branch 'master' into aureplop/cof-tests_registration 2018-04-07 13:20:33 +02:00
Martin Pepin 3463017d59 build status in README.me 2018-04-07 12:54:50 +02:00
Martin Pepin 9986cb1dc7 Merge branch 'aureplop/cof-tests_misc' into 'master'
cof -- Add tests for some views

See merge request cof-geek/gestioCOF!292
2018-04-07 12:46:25 +02:00
Martin Pépin 71a61fe31d Merge branch 'master' into aureplop/cof-tests_misc 2018-04-07 12:41:42 +02:00
Martin Pepin 0cceed9052 Merge branch 'aureplop/cof-tests_export' into 'master'
cof -- Add tests for export views

See merge request cof-geek/gestioCOF!291
2018-04-07 12:15:06 +02:00
Martin Pépin 6328cdaa19 Tests: the order of our csv files is not relevant 2018-04-07 12:05:16 +02:00
Martin Pépin ee33762845 Merge branch 'master' into aureplop/cof-tests_export 2018-04-07 11:24:05 +02:00
Martin Pepin 2d14296c47 Merge branch 'aureplop/cof-tests_club' into 'master'
cof -- Add tests for club views

See merge request cof-geek/gestioCOF!289
2018-04-07 10:54:31 +02:00
Martin Pépin 660f395b67 Merge branch 'master' into aureplop/cof-tests_club 2018-04-07 10:49:52 +02:00
Martin Pepin f23d351ddc Merge branch 'aureplop/cof-tests_calendar' into 'master'
cof -- Add tests for calendar views

See merge request cof-geek/gestioCOF!290
2018-04-07 10:33:35 +02:00
Martin Pépin 60d8e76fee Merge branch 'master' into aureplop/cof-tests_calendar 2018-04-07 10:24:41 +02:00
Martin Pépin 623047dca2 Fix old-style reversal of calendar urls 2018-04-06 11:11:02 +02:00
Martin Pepin 16d7b4f7c0 Merge branch 'aureplop/cof-tests_event' into 'master'
cof -- Add tests for event views

See merge request cof-geek/gestioCOF!286
2018-04-06 00:24:23 +02:00
Martin Pépin bf464f9378 Merge branch 'master' into aureplop/cof-tests_event 2018-04-06 00:16:08 +02:00
Martin Pépin 35e17a81a6 New year -> new promo -> migration in k-fet 2018-04-05 23:48:53 +02:00
Qwann cc27e4d964 show not sold article in a different list 2018-03-22 15:25:03 +01:00
Evarin 3d091f50b5 Suppr captcha page, ajout block iframe 2018-03-21 21:53:48 +01:00
Evarin 347497602c Revert urls prefixing 2018-02-20 17:21:26 +01:00
Evarin 6d6c995563 Fixtures fonctionnelles 2018-02-20 17:13:21 +01:00
Martin Pepin 307c48ca76 Merge branch 'elarnon/fix-182' into 'master'
Properly propagate the default number of places in tirage

Closes #182

See merge request cof-geek/gestioCOF!295
2018-02-20 14:40:20 +01:00
Martin Pepin cadbd1b7c2 Merge branch 'elarnon/compat_fixes' into 'master'
Various fixes for Django 1.11

See merge request cof-geek/gestioCOF!294
2018-02-20 14:38:21 +01:00
Martin Pepin 746c5e2053 Merge branch 'elarnon/bash_set_e' into 'master'
Make provisioning script stop immediately on errors

See merge request cof-geek/gestioCOF!293
2018-02-20 14:36:28 +01:00
Basile Clement 6ecc9a54b3 Properly propagate the default number of places in tirage
Fixes #182.
2018-02-11 19:24:01 +01:00
Basile Clement 3314670cab Various fixes for Django 1.11
- The {% cycle %} command was used non-quoted arguments separated by
   commas, while it is supposed to use quoted arguments separated by
   spaces (I'm actually not sure how that ever worked :)

 - django-bootstrap-form was at version 3.2.1 which is not compatible
   with Django 1.11 (but also required by GestioCOF). I upgraded it to
   version 3.3.
2018-02-11 19:09:07 +01:00
Basile Clement ac1a57d969 Make provisioning script stop immediately on errors
By default, bash will ignore any failing commands and happily proceed to
execute the next ones. This is usually not the behavior the we want in
provisioning script (or ever in scripts, actually): if one step of the
provisioning fails, it doesn't make much sense to proceed with the
following ones.

This simple patch uses `set -e` to ask bash to abort the whole script if
any command within it fails, leading to outputs that are easier to parse
since the commands following a failing one will usually fail also,
hiding the root cause.
2018-02-11 17:01:26 +01:00
Martin Pepin 8f0eec0e88 Merge branch 'aureplop/cof-tests_survey' into 'master'
cof -- Add tests for survey views

See merge request cof-geek/gestioCOF!285
2018-02-06 13:48:46 +01:00
Martin Pepin d88ce44989 Merge branch 'aureplop/fix-autocomplete-js' into 'master'
Fix autocomplete in registration views.

See merge request cof-geek/gestioCOF!288
2018-02-05 22:49:18 +01:00
Martin Pepin c94a9ecb44 Merge branch 'aureplop/kfet_initial-statement' into 'master'
kfet -- Create initial statement on checkout save

See merge request cof-geek/gestioCOF!283
2018-02-05 22:42:04 +01:00
Martin Pépin d57c75d2a0 Minor simplificatons after code review 2018-02-05 22:36:08 +01:00
Martin Pepin a3295ca6a3 Merge branch 'aureplop/fix-184' into 'master'
Fix available checkouts in K-Psul

Closes #184

See merge request cof-geek/gestioCOF!282
2018-02-05 22:35:04 +01:00
Martin Pepin 09433f6f15 Merge branch 'aureplop/speed-up-tests' into 'master'
Speed up tests in dev (×40)

See merge request cof-geek/gestioCOF!281
2018-02-05 22:18:52 +01:00
Martin Pepin 8c68270a4d Merge branch 'aureplop/Kerl/drop_py2_compat' into 'Kerl/drop_py2_compat'
Py3 allows to shorten super()

See merge request cof-geek/gestioCOF!284
2018-02-05 22:14:04 +01:00
Evarin 63ce694b4d Fix dependency 2018-02-03 22:39:50 +01:00
Evarin 59116f2d46 International wagtail urls 2018-02-03 22:34:37 +01:00
Evarin 88e911ff9d Fixtures mises à jour 2018-01-28 23:45:46 +01:00
Evarin ba87044638 Calendrier dynamique + sympa + jolies dates 2018-01-28 23:44:48 +01:00
Evarin f8952225d6 Apparence et Responsiveness 2018-01-28 19:10:14 +01:00
Evarin c11ccf2ecc Tri des annuaires 2018-01-28 19:09:35 +01:00
Aurélien Delobelle afa6972280 Better handling of non-authorized users in config edition view 2018-01-22 21:59:41 +01:00
Aurélien Delobelle 0235c4f7e8 Fix profile edition view
- Fix a typo.
- Bump version of django-bootstrap-form to be comaptible with Django
1.11.
2018-01-22 21:59:41 +01:00
Aurélien Delobelle f8361b9114 Add & fix urls naming 2018-01-22 21:59:41 +01:00
Aurélien Delobelle 0876a004e5 Name urls of export views (cof members, mega) 2018-01-22 21:59:41 +01:00
Aurélien Delobelle 91162addb9 cof -- Add tests for some views 2018-01-22 21:59:41 +01:00
Aurélien Delobelle bd89dce11d Add testing helpers to create superuser 2018-01-22 21:38:01 +01:00
Evarin 8488beeb4e Un seul modèle pour les actus 2018-01-22 21:24:20 +01:00
Aurélien Delobelle a813507ddd Name urls of export views (cof members, mega) 2018-01-22 14:59:57 +01:00
Aurélien Delobelle f371606cdb cof -- Add tests for export views 2018-01-22 14:58:38 +01:00
Aurélien Delobelle 80ca35a4c0 Add helper to check HttpResponse containing csv 2018-01-22 14:49:02 +01:00
Aurélien Delobelle 38539a9d53 Name url to export calendar to ical 2018-01-21 18:19:43 +01:00
Aurélien Delobelle acf284862a Users should be able to refuse to subscribe to shows and events 2018-01-21 18:17:27 +01:00
Aurélien Delobelle 2e6a54c7db cof -- Add tests for calendar views 2018-01-21 18:17:26 +01:00
Aurélien Delobelle 7e0ecd8e0f Add assertion to check ical data is as expected 2018-01-21 18:17:26 +01:00
Aurélien Delobelle bbe46645f7 cof -- Fix the club list view 2018-01-20 22:24:25 +01:00
Aurélien Delobelle a5071aa257 cof -- Add tests for club views 2018-01-20 22:24:13 +01:00
Evarin 8551ffcfd3 Merge branch 'master' into evarin/site-cof 2018-01-20 19:37:12 +01:00
Evarin ea495e8f29 Archives beta 2018-01-20 19:33:50 +01:00
Aurélien Delobelle 4084444dc3 Fix autocomplete in registration views.
django-autocomplete-light v3.x doesn't include anymore the
$('').yourlabsAutocomplete() function, leading to issues in cof
registration and kfet account creation views.

Adding jquery-autocomplete-light fixes these issues.

See:
- (dal) https://github.com/yourlabs/django-autocomplete-light
- (jal) https://github.com/yourlabs/jquery-autocomplete-light
2018-01-20 17:29:15 +01:00
Aurélien Delobelle 7bf2f73e72 Merge branch 'aureplop/cof_tests' into aureplop/cof-tests_registration 2018-01-20 17:02:54 +01:00
Aurélien Delobelle c239f28f17 syncmails should be able to be silent 2018-01-20 17:02:23 +01:00
Aurélien Delobelle 0921f32e4c cof -- Fix urls naming related to registration 2018-01-20 16:17:57 +01:00
Aurélien Delobelle 7160a9c954 cof -- Add tests for registration views 2018-01-20 16:14:55 +01:00
Aurélien Delobelle dfb9ccb0af Fix use of Widget.build_attrs in TriStateCheckbox
Signature changed in Django 1.11.
2018-01-19 18:41:06 +01:00
Aurélien Delobelle a6f52cfdc5 cof -- Fix urls naming in event template 2018-01-19 18:38:34 +01:00
Aurélien Delobelle f5b280896f cof -- Add tests for event views 2018-01-19 18:36:03 +01:00
Aurélien Delobelle ce73499077 Fix use of Widget.build_attrs in TriStateCheckbox
Signature changed in Django 1.11.
2018-01-19 18:15:57 +01:00
Aurélien Delobelle 8675948d9e cof -- Fix urls naming in survey templates 2018-01-19 18:01:36 +01:00
Aurélien Delobelle 57de31d59a cof -- Add tests for survey views 2018-01-19 17:57:43 +01:00
Aurélien Delobelle 776ff28141 cof -- Add helpers to test cof views. 2018-01-19 17:52:08 +01:00
Aurélien Delobelle 42e762bc4a Py3 allows to shorten super() 2018-01-16 16:50:27 +01:00
Aurélien Delobelle 478f56d94b kfet -- Create initial statement on checkout save
- Why? Because it should be the actual behavior.
- To allow using arithmetic operations with values of DecimalField when
object are not retrieved from DB, some strings are replaced by Decimal
or int.
If you wonder why it's not automatically done, see:
https://code.djangoproject.com/ticket/27825
2018-01-16 16:49:02 +01:00
Aurélien Delobelle 525bb4d16d kfet -- Fix available checkouts in K-Psul
The checkout validity is checked using the current datetime (when
requesting the kpsul page).
2018-01-15 17:03:57 +01:00
Aurélien Delobelle e23e1bdba6 kfet -- Add test to check the choices of checkouts in K-Psul
Particularly, it adds a regression test for #184.
2018-01-15 17:01:06 +01:00
Aurélien Delobelle 6059ca067b Speed up tests
~20% less using MD5 and force_login in kfet testcase.
~77% less by disabling the debug tollbar.
2018-01-15 05:41:51 +01:00
Martin Pépin 79fccc136a Merge branch 'master' into Kerl/tests 2018-01-10 20:21:38 +01:00
Ludovic Stephan 52fd49616d Fix model test 2018-01-10 20:14:27 +01:00
Martin Pépin 5a5b60ec4d Merge branch 'master' into Kerl/drop_py2_compat 2018-01-10 20:12:59 +01:00
Ludovic Stephan 501d592d2f Merge branch 'master' into Aufinal/bda_fixes 2018-01-10 20:00:34 +01:00
Martin Pépin 91119f68bc Ne pas oublier avant de passer en prod… 2018-01-10 17:34:41 +01:00
Aurélien Delobelle 44eee9be38 Merge branch 'aureplop/py34-compat' into 'master'
py34 compat

See merge request !279
2018-01-10 17:30:44 +01:00
Aurélien Delobelle f58f120e7a py34 compat
(already present in prod)
2018-01-10 17:25:07 +01:00
Martin Pepin 771b642a98 Merge branch 'aureplop/revente-reply_to' into 'master'
bda -- Set winner's email as Reply-to of the email…

Closes #179

See merge request !276
2018-01-07 16:03:20 +01:00
Martin Pépin 5086128f16 Fix ill-formed url in tests 2018-01-07 14:45:04 +01:00
Martin Pépin c80e63415b Load custommails before bda tests 2018-01-07 14:30:33 +01:00
Martin Pépin e9b901337e A few tests for BdA views 2018-01-07 13:35:23 +01:00
Martin Pepin 433b3f4716 Merge branch 'aureplop/1.11' into 'master'
Upgrade to Django 1.11

See merge request !275
2018-01-06 17:00:28 +01:00
Martin Pépin 62d8c2ffaf remove @py2_unicode_compat + six 2018-01-06 16:10:13 +01:00
Martin Pépin 97eed06b6f Remove builtins imports 2018-01-06 16:10:13 +01:00
Martin Pépin 57411ab46f Remove __future__ imports 2018-01-06 16:10:13 +01:00
Martin Pépin 475f1adec5 Remove "coding: utf8" line 2018-01-06 16:10:13 +01:00
Ludovic Stephan 9a8773978c Use new method in admin 2017-12-19 12:50:20 +01:00
Ludovic Stephan 1783196a9c Management view only deals with Revente objects
Except for Revente creation, every form is now handled with revente
objects, to use the display option in the previous commit.
2017-12-19 12:41:50 +01:00
Ludovic Stephan f1bbade002 Better labels for revente objects
The label for the ReventeModelMultipleChoiceField now depends on a
`own` parameter, which determines if we display the seller or the
buyer's name.
2017-12-19 12:40:50 +01:00
Ludovic Stephan dfa8c1a1a1 Merge branch 'Aufinal/bda_fixes' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/bda_fixes 2017-12-19 11:54:07 +01:00
Ludovic Stephan 5f9f222cda Merge branch 'Kerl/bda_fixes' into 'Aufinal/bda_fixes'
Kerl/bda fixes

See merge request !273
2017-12-19 11:53:31 +01:00
Ludovic Stephan 212528011a Add some tests 2017-12-19 11:40:02 +01:00
Aurélien Delobelle 32720c56a6 kfet -- Tables are sortable
Many tables in kfet app templates become sortable:
account list, negative account list, article list, article inventory
list, article supplier list, article category list, checkout list,
checkout statement list, inventory list, inventory details, order list,
order creation, order details.

This is achieved thanks to the jQuery plugin 'tablesorter':
https://mottie.github.io/tablesorter/docs/

- Affected tables also got sticky headers (it stays visible on scroll).
- Dates format are modified in order to ease the date sorting with the
plugin (it avoids writing a custom parser, or an extractor from
additional hidden element in the table cells).
- Tables whose content is classified by category (of articles) now uses
several tbodies. This has minor effects on the tables style.
- Tags of the header help signs become 'i', instead of 'span', in order
to avoid weird spacing.
2017-11-27 18:24:22 +01:00
Aurélien Delobelle 241c77e3b4 bda -- Set winner's email as Reply-to of the email…
…sent to the seller when a resale has been assigned.

Fixes #179.
2017-11-24 06:58:33 +01:00
Aurélien Delobelle b0b0542407 Upgrade to Django 1.11
- Deprecation warnings using Django 1.8 are resolved.
- Deprecation warnings using Django 1.11 are resolved.

- Admin: grappelli is no longer used.
- Upgrade to django-autocomplete-light v3 (v2 is not 1.11 compatible).
  * autocomplete.modelform_factory being dropped, code uses dal Select2
    views and widgets.
2017-11-19 18:41:39 +01:00
Aurélien Delobelle 36ce038050 Merge branch 'Qwann/legibility_kfet' into 'master'
Qwann/legibility kfet

See merge request !272
2017-11-10 14:54:17 +01:00
Martin Pépin 91bdf11852 Coding style: python's scope sucks 2017-11-01 17:29:38 +01:00
Martin Pépin f18959c0a1 BdA-Revente: meaningful names, some help tests 2017-11-01 17:26:40 +01:00
Martin Pépin e1794a654f Merge branch 'master' into Aufinal/bda_fixes 2017-11-01 11:25:52 +01:00
Martin Pépin 273e6374ef Pluralization in bda -> participant list 2017-11-01 11:09:35 +01:00
Qwann 93fa79128c order table striped 2017-10-31 15:10:21 +01:00
Martin Pepin 19e6ddc8bf Merge branch 'aureplop/delete-kfet-globalperms' into 'master'
Delete GlobalPermissions model (migrations)

See merge request !270
2017-10-27 10:04:17 +02:00
Aurélien Delobelle 895f7e062c Delete GlobalPermissions model (migrations)
It is an old model which doesn't exist anymore in kfet.models module.

This adds its missing DeleteModel in migrations.
2017-10-27 03:38:28 +02:00
Martin Pépin 1c90d067fa Make cof.settings a module 2017-10-26 18:13:09 +02:00
Ludovic Stephan 785555c05c Misc fixes 2017-10-26 12:40:11 +02:00
Aurélien Delobelle 364648fb4f Merge branch 'Kerl/cours' into 'master'
initialisation des compteurs d'attributions de petits cours et settings.EMAIL_HOST

Ce patch règle deux problèmes en prod + un bug seulement présent en dev :

- Mauvaise initialisation des compteurs d'attributions de petits cours lors de la création d'un matière (premier compteur lié à cette matière)
- absence de `EMAIL_HOST` dans les settings => problèmes en production : la valeur par défaut `localhost` n'est pas satisfaisante.
- Il manquait un `Type` de variable dans la fixture des mails auto (dev seulement, visiblement pas de souci en production).

See merge request !265
2017-10-26 01:57:01 +02:00
Martin Pépin 1a136088bf Add missing type in custommail (dev only) 2017-10-25 22:08:29 +02:00
Martin Pépin 40abe27e81 EMAIL_HOST needs to be set but as a secret 2017-10-25 22:05:14 +02:00
Martin Pépin a07b5308a3 PetitCoursAttributionCounter defaults to 0 2017-10-25 22:01:58 +02:00
Aurélien Delobelle 8e8e9aa076 Fix migration history 2017-10-24 19:25:20 +02:00
Aurélien Delobelle 53ef8b517a Merge branch 'test/views' 2017-10-24 18:01:49 +02:00
Aurélien Delobelle af3a7cf697 Reapply fix to kfetauth (…) and fix tests 2017-10-24 17:56:14 +02:00
Aurélien Delobelle 1cc51f17a3 Prevent connection to LDAP when settings is None 2017-10-24 17:55:02 +02:00
Aurélien Delobelle 8673da1874 Fix migration conflict 2017-10-24 16:52:57 +02:00
Aurélien Delobelle 8b1f174b13 manage.py is executable 2017-10-24 16:46:15 +02:00
Aurélien Delobelle 8cdb4c62fc Merge branch 'master' into test/views 2017-10-24 16:45:26 +02:00
Ludovic Stephan 6a6549e0d7 Add notif time
In case of a gestioCOF bug, we keep the notification time in memory to
still do the drawing 1-3 days after.
2017-10-23 20:55:01 +02:00
Ludovic Stephan 684603709e Class attributes and properties + more verbose log
SpectacleRevente gets brand new properties and attributes to simplify
code ; also, manage_reventes command output is more verbose
2017-10-23 20:30:34 +02:00
Ludovic Stephan 1b0e4285ec Reverse match fix 2017-10-23 20:26:07 +02:00
Ludovic Stephan 919bcd197d Small code QoL improvements 2017-10-23 18:59:30 +02:00
Ludovic Stephan e74dbb11f1 Organize revente files and function names 2017-10-23 18:39:45 +02:00
Ludovic Stephan 732e47707e Add unsubscribe option + list of current draws 2017-10-23 17:25:58 +02:00
Evarin adf43889e1 Fixtures site cof 2017-10-23 11:05:49 +02:00
Martin Pepin cb820e1414 Merge branch 'aureplop/fix-tirage-pk-conflict' into 'test/views'
Fix tirage pk conflicts with postgres

See merge request !262
2017-10-18 11:40:02 +02:00
Aurélien Delobelle 46187659ed Fix tirage pk conflicts with postgres 2017-10-17 14:42:39 +02:00
Martin Pépin fccad5edee rename root -> kfet_genericteam in fixtures 2017-10-16 14:31:02 +02:00
Martin Pépin b9aaea0f99 Merge branch 'master' into Production 2017-10-16 14:26:05 +02:00
Martin Pepin 47e48e36b2 Merge branch 'aureplop/fix-unreachable-objects-form-creation' into 'test/views'
Fix fields cleaning with unreachable items when…

See merge request !260
2017-10-12 19:21:16 +02:00
Aurélien Delobelle 85657591f5 Fix fields cleaning with unreachable items when…
… object is being created.
2017-10-12 11:10:30 +02:00
Aurélien Delobelle c59bc487c3 Merge branch 'test/views__' into test/views 2017-10-11 23:35:38 +02:00
Aurélien Delobelle f4a7e9dbf1 Verbosity should stay calm. 2017-10-11 23:34:43 +02:00
Aurélien Delobelle 03deb54d92 Merge branch 'Kerl/fix-JSONField-exn' into 'master'
djangorestframework 3.7 breaks with Django 1.8

See merge request !259
2017-10-11 23:18:05 +02:00
Martin Pépin 3b1d8487e2 Merge branch 'aureplop/kfet-auth_backends' 2017-10-11 22:59:28 +02:00
Martin Pépin f8bb5b081e Merge branch 'Kerl/fix-JSONField-exn' into test/views 2017-10-10 21:37:40 +02:00
Martin Pépin 3f6c5be748 Upgrade python packages before testing 2017-10-10 21:27:15 +02:00
Martin Pépin e0ab7f5f94 Fix migration conflict 2017-10-10 21:21:28 +02:00
Martin Pépin 116b4da1a5 Merge branch 'test/views_kfet' into test/views 2017-10-10 21:15:49 +02:00
Martin Pépin 29ef297b2a try to set the redis password… 2017-10-10 21:14:52 +02:00
Martin Pépin 503b305299 djangorestframework 3.7 breaks with Django 1.8
JSONField doesn't exist in Django 1.8
2017-10-10 18:34:18 +02:00
Martin Pépin 3d22a1b029 Merge branch 'master' into test/views 2017-10-10 15:39:18 +02:00
Martin Pépin 528532cca7 Merge branch 'aureplop/fix-ci' 2017-10-10 15:35:22 +02:00
Martin Pépin 4d1cb3c2d7 Set password for redis in CI 2017-10-10 15:26:14 +02:00
Evarin 5a22b1cd37 Affichage des actus 2017-10-10 11:22:02 +02:00
Aurélien Delobelle 57a143e0e1 Merge branch 'PEI' into 'master'
Statut PEI + cotisation gratuite

See merge request !258
2017-10-06 11:06:44 +02:00
Martin Pépin 435e211b3d Add a "PEI" status + "Gratis" subscription fees 2017-10-02 13:58:52 +02:00
Aurélien Delobelle 596868f5b6 plop 2017-09-30 03:03:48 +02:00
Evarin 0e19abb51a Cleaner homepage 2017-09-27 23:58:50 +02:00
Martin Pépin b73faa3b84 Merge branch 'master' into Production 2017-09-25 18:40:00 +02:00
Martin Pepin 22413d861c Merge branch 'aureplop/fix-attributions-admin' into 'master'
Fix attribution inlines of participant in admin

See merge request !255
2017-09-25 18:38:05 +02:00
Aurélien Delobelle d18fb86a98 Fix attribution inlines of participant in admin 2017-09-25 18:26:54 +02:00
Aurélien Delobelle b42452080f Mass cleaning of kfet' authentication machinery
AccountBackend
- Should now work if used in AUTHENTICATION_BACKENDS settings.
- It does not retieve itself the password, as it should not be used
this way.

GenericBackend
- Delete useless 'username' arg of its 'authenticate()' method.
- Now delete the token in DB.

TemporaryAuthMiddleware
- New name of the middleware is more meaningful.
- Is now responsible to retrieve the password from the request, instead
of the AccountBackend.

GenericTeamToken model
- Add a manager' method to create token, avoiding possible error due to
unicity constraint.

GenericLoginView (authentication with the kfet generic user)
- Replace obscure system with a 100% HTTP handling.
- See comments for more information.

Misc
- More docstrings!
- More tests!
- Add some i18n.
- Add kfet/confirm_form.html template:
    Ask user to confirm sth via a form (which will send a POST request).
    Context variables:
        * title: the page title
        * confirm_url: action attribute for <form>
        * text: displayed confirmation text
- kfet.js : Add functions allowing to emit POST request from <a> tag.
- Non-link nav items from kfet navbar also get a 'title'.
- A utility has been found for the 'sunglasses' glyphicon!
2017-09-25 17:57:47 +02:00
Aurélien Delobelle 11e8cb1be2 Merge branch 'bdaAdminHotfix' into 'master'
Hotfix : répare l'admin, le champ "given" n'était pas affiché quand il faut

See merge request !254
2017-09-25 15:01:28 +02:00
Martin Pépin db512a97f6 In /admin: displays "given" when it's relevant 2017-09-25 14:22:46 +02:00
Aurélien Delobelle 3fa7754ff4 KFet Backends inherit from BaseKFetBackend
Users who authenticate via a KFetBackend got extra select related.
It should save 2 db queries on each request for these users.
2017-09-23 20:48:28 +02:00
Aurélien Delobelle e5d19811e8 Clean code related to kfet password 2017-09-22 23:31:46 +02:00
Aurélien Delobelle 1d19d1797c Clean setup/retrieve of kfet generic account 2017-09-22 01:24:44 +02:00
Martin Pépin 6f2652c485 Prod quick hack for Mega export 2017-09-20 18:23:36 +02:00
Martin Pépin d89ba1efe5 Fix catalogue behaviour if id=0 2017-09-20 18:21:59 +02:00
Martin Pépin 4091185a68 import LDAP_SERVER_URL in settings 2017-09-20 18:19:15 +02:00
Aurélien Delobelle bf61e41b50 Move auth-related from 'kfet' app to 'kfet.auth'. 2017-09-19 17:05:51 +02:00
Hugo Roussille a4eedbc1a6 Whitelist bda and cof apps for cross-domain 2017-09-13 18:21:34 +02:00
Hugo Roussille 4bd2562edf django-cors-headers for cross-domain AJAX 2017-09-13 15:57:57 +02:00
Martin Pepin 7d16001ee5 Merge branch 'aureplop/fix_kfet_perms' into 'master'
K-Fêt' groups edits don't remove non-kfet app permissions.

Closes #168

See merge request !252
2017-09-12 22:35:30 +02:00
Martin Pépin 1921f05eba Move STATIC_ROOT in production 2017-09-12 09:22:54 +02:00
Martin Pépin 368ee3190f Update CI: use postgres 2017-09-12 00:14:52 +02:00
Aurélien Delobelle 9e61887868 K-Fêt' groups edits don't remove non-kfet app permissions.
Fixes #168.
2017-09-11 16:42:14 +02:00
Martin Pépin faed7bff73 fix ?next=… on K-Fêt logout 2017-09-10 02:32:21 +02:00
Martin Pepin f581279825 Merge branch 'aureplop/fix-kfet-navbar' into 'master'
Fix kfet navbar on small devices

See merge request !248
2017-09-10 02:12:49 +02:00
Martin Pépin 937a485704 Merge branch 'master' into Production 2017-09-09 22:03:32 +02:00
Martin Pépin 439f49c3ba We ♥ hardcoding stuff… 2017-09-05 15:21:19 +02:00
Martin Pépin 35b352ac1d Fix mistake introduced in 51f4bf3fb5 2017-09-04 14:50:12 +02:00
Martin Pépin 50432d969f Update available promos for account creation 2017-09-04 13:25:45 +02:00
Martin Pépin 51f4bf3fb5 Clipper logins may be > 8 characters 2017-09-04 13:25:09 +02:00
Aurélien Delobelle fb5ba5fb1b Fix kfet navbar on small devices 2017-09-03 14:42:38 +02:00
Aurélien Delobelle af97c0cda6 Improve users management on kfet TestCase, and Py34 compat 2017-09-01 16:37:14 +02:00
Aurélien Delobelle 997b63d6b6 More docs for kfet.tests.utils 2017-09-01 13:35:32 +02:00
Aurélien Delobelle 7d490f0253 Merge branch 'test/views' into test/views_kfet 2017-09-01 12:41:31 +02:00
Aurélien Delobelle 5220bb75d0 Merge branch 'test/views_kfet' of git.eleves.ens.fr:cof-geek/gestioCOF into test/views_kfet 2017-09-01 12:40:37 +02:00
Aurélien Delobelle d8391e54a5 Add docs to kfet TestCases 2017-09-01 12:39:17 +02:00
Martin Pepin b7502e51ed Merge branch 'aureplop/amend_supplier_model' into 'test/views_kfet'
Most data of suppliers should be optionnal.

See merge request !246
2017-08-30 20:32:43 +02:00
Aurélien Delobelle be1e67626c Most data of suppliers should be optionnal. 2017-08-30 15:35:20 +02:00
Martin Pepin 0afbd577b1 Merge branch 'aureplop/fix_view_account_search' into 'test/views_kfet'
View 'search account' should be restricted.

See merge request !245
2017-08-29 20:41:49 +02:00
Martin Pepin afda1ba2ca Merge branch 'test/views_fix_msg' into 'test/views'
Fix kfet.open.tests

See merge request !241
2017-08-29 20:39:31 +02:00
Martin Pepin 6e140e540d Merge branch 'aureplop/fix_perms_settings' into 'test/views_kfet'
Fix kfet config-related permissions

See merge request !244
2017-08-29 20:37:48 +02:00
Evarin 38af50a866 Nouvelles couleurs 2017-08-29 17:58:44 +02:00
Evarin 7853987ebb Plus de templates, plus joli 2017-08-26 18:05:20 +02:00
Evarin 09e63bf00c Actus et listes de clubs plus jolies et fonctionnelles, calendriers (beta) 2017-08-22 00:58:18 +02:00
Evarin 53658589f8 Nouvelles couleurs, Plus de templates, Calendrier (sommaire) 2017-08-20 00:39:19 +02:00
Evarin f5778fed2a Modèles plus cleans et templates principaux 2017-08-19 01:32:26 +02:00
Evarin 66fc364739 Ignore sass cache 2017-08-19 01:29:45 +02:00
Aurélien Delobelle b4338ce8db View 'search account' should be restricted. 2017-08-16 22:54:40 +02:00
Aurélien Delobelle 22d8317dee Fix kfet.open.tests
Due to messages sent in signals handlers, the tests were failing.
2017-08-16 22:43:52 +02:00
Aurélien Delobelle b4b15ab371 Tests of kfet config views pass 2017-08-16 22:30:17 +02:00
Aurélien Delobelle 414b0eb433 Add missing perms to view/edit kfet config 2017-08-16 21:28:16 +02:00
Aurélien Delobelle a3bb9852be Merge branch 'master' into test/views 2017-08-16 18:28:59 +02:00
Aurélien Delobelle 2cfce1c921 Add tests for kfet views.
kfet.tests.testcases embed mixins for TestCase:
- TestCaseMixin provides assertion helpers,
- ViewTestCaseMixin provides a few basic tests, which are common to
every view.

kfet.tests.utils provides helpers for users and permissions management.

Each kfet view get a testcase (at least very basic) in
kfet.tests.test_views.
2017-08-16 17:45:59 +02:00
Aurélien Delobelle 343b52f986 Merge branch 'master' into test/views_kfet 2017-08-14 21:18:46 +02:00
Aurélien Delobelle 1d9310236b Merge branch 'Kerl/setup' into 'master'
Setup de développement local

Développement sans Vagrant

- Ajoute un fichier de settings pour développer en local sans vagrant :
  'cof/settings/local.py'. Il s'agit du fichier utilisé par défaut
  si 'DJANGO_SETTINGS_MODULE' n'est pas dans l'environnement.
- Simplifie le README en conséquence.

Vagrant

La conf de production étant en cours de modification, on répercute ici
certaines de ces modifications.

- Front server: passe à nginx (à la place de apache).
- DB: passe à postgresql (à la place de mysql).
- La conf nginx sert le site (port 8080) derrière '/gestion/', à
  l'exception des fichiers 'static' et 'media'.
- Ajout de `SERVER_EMAIL` dans les secrets pour pouvoir l'éditer en
  production.
- Changement de `STATIC_ROOT` en production.

See merge request !242
2017-08-13 21:17:26 +02:00
Martin Pépin 2a519bfedf Add SCRIPT_NAME to the production-like server 2017-08-13 14:36:45 +01:00
Martin Pépin 4c08962e09 Hide more stuff in secret.py 2017-08-13 13:39:13 +01:00
Martin Pépin 4d026407d1 Quicker setup for local development 2017-08-11 23:56:02 +01:00
Martin Pépin 5923166196 Specify the full path of the sqlite database 2017-08-11 17:24:09 +01:00
Martin Pépin 853fa57ce4 In README.md: typo env -> venv 2017-08-11 15:45:19 +01:00
Martin Pépin 522acafb2e Add python dependency for Debian9 2017-08-10 16:56:20 +01:00
Martin Pépin dae418af3d Handle websockets in nginx.conf 2017-08-10 16:56:03 +01:00
Martin Pépin 73296ea251 Setup django before launching daphne + Django 2017-08-10 16:55:36 +01:00
Martin Pépin 4075fcaa64 typo 2017-08-10 16:35:41 +01:00
Martin Pépin bd1dace8e8 Fix symlink for secret.py 2017-08-10 16:31:09 +01:00
Aurélien Delobelle c9aac8a49d [WIP] Tests for kfet views 2017-08-10 15:02:08 +02:00
Martin Pépin ad15c45237 Switch to nginx + postgres in vagrant 2017-08-09 22:18:26 +01:00
Martin Pépin cb1d253517 Local development settings
- Add a new settings file for local development
- Update README.md according to the new setup
2017-08-09 22:17:27 +01:00
Evarin 6023211ab0 Models des pages et traductions 2017-08-09 00:07:56 +02:00
Martin Pépin a6b0c51d39 Add SERVER_EMAIL to the secrets 2017-08-08 00:25:13 +01:00
Martin Pépin 81f3d6ab81 Move STATIC_ROOT in production 2017-08-08 00:24:00 +01:00
Martin Pépin 062f547315 Use the right python interpreter in the cron tasks 2017-08-08 00:19:47 +01:00
Martin Pépin 784513b3cc Use utf8 encoding for the mysql database 2017-08-08 00:12:09 +01:00
Martin Pépin 88597e62f1 More verbose secret error reporting 2017-08-08 00:06:03 +01:00
Martin Pépin e13d68a127 Ignore PyCharm's files 2017-08-08 00:03:08 +01:00
Evarin 65d7a66eb8 Début nouveau site cof 2017-08-07 23:31:27 +02:00
Aurélien Delobelle 878c617cc7 fix PermConsumerMixin 2017-06-25 22:29:12 +02:00
Martin Pépin 68c1b45342 Add missing migrations 2017-06-25 16:30:49 +01:00
Martin Pepin d30b36269a Merge branch 'aureplop/kfet_cms' into 'master'
Add Wagtail CMS for kfet app.

See merge request !237
2017-06-25 16:47:48 +02:00
Aurélien Delobelle 455b730cc3 Merge branch 'master' into aureplop/kfet_cms 2017-06-23 02:53:05 +02:00
Martin Pepin b49d96d18f Merge branch 'aureplop/kfet_open' into 'master'
La K-Fêt est-elle ouverte ?

See merge request !239
2017-06-23 00:28:58 +02:00
Aurélien Delobelle 815ba50603 fix for small devices 2017-06-22 16:59:41 +02:00
Aurélien Delobelle 5673fabeff Better status management.
Status is mainly computed in Python. That fix inconsistent datetime between
client and server.

Client only receives status and keep timestamp of last received ws msg.
2017-06-22 16:36:08 +02:00
Aurélien Delobelle 19847ac9d8 add token check to raw_open edit view 2017-06-22 15:48:45 +02:00
Aurélien Delobelle 98f5f0c391 update refresh/unknown interval 2017-06-22 05:44:05 +02:00
Aurélien Delobelle 782e105644 typo 2017-06-21 23:31:27 +02:00
Aurélien Delobelle b8110c11a4 kfet.open
kfet.open app
- Base data (raw_open, last_update...) is stored and shared through cache system.
- 2 websockets groups: one for team users, one for other users.
- UI is initialized and kept up-to-date with WS.
- raw_open and force_close can be updated with standard HTTP requests.
  At this time, there isn't any restriction on raw_open view. Common sense tell us
  to change this behavior.

Misc
- Clean channels routing.
- 'PermConsumerMixin': user who sent the message is available as argument in
connection_groups method, which returns groups to which the user should be
appended on websocket connection (and discarded on disconnection).
- New kfet.utils module: should be used for mixins, whatever is useful and not concerns
the kfet app.
- Clean JS dependencies.
2017-06-21 07:08:28 +02:00
Aurélien Delobelle 2381af92e3 Merge branch 'master' into aureplop/kfet_open 2017-06-21 05:47:53 +02:00
Aurélien Delobelle 398893b904 Merge branch 'Kerl/mail2bda' into 'master'
Copie des mails de rappel pour le BdA

* Le BdA est en copie des mails de rappel (création d'un utilisateur générique BdA à cette fin)
* Un lien vers la page d'envoi manuel des mails de rappel est ajouté à la page qui récapitule les situations des participants au spectacle
* Quelques modification mineures de cette page
* Un test très simple vérifie que les pages mentionnées plus haut sont accessibles à un utilisateur COF authentifié

See merge request !238
2017-06-21 05:43:42 +02:00
Martin Pepin 539c32dbbe Merge branch 'aureplop/fix_cache' into 'master'
Configure un système de cache

See merge request !221
2017-06-20 15:53:29 +02:00
Martin Pépin 80f6a808dc Simplify the mails-rappel view 2017-06-18 17:03:50 +01:00
Martin Pépin 69aee6b8ac Move bda.get_generic_user + optimize send_rappels 2017-06-18 16:52:53 +01:00
Aurélien Delobelle ec59bc2edc Merge branch 'master' into aureplop/kfet_open 2017-06-12 15:18:42 +02:00
Aurélien Delobelle d004287957 Merge branch 'master' into aureplop/kfet_cms 2017-06-12 02:04:28 +02:00
Aurélien Delobelle 1499c0bced Improvements for K-Fêt CMS.
K-Fêt - Wagtail
- Page content becomes a StreamField.
- GroupTeam snippet becomes a block for stream field.
- Navigation menu moved becomes a "flatmenu", preventing possible future conflicts.
- Page layout can be modified in wagtail admin.

K-Fêt
- Add shorthands for ukf account balance/article price.
- Cleaning stylesheets and templates.
2017-06-12 01:51:10 +02:00
Martin Pépin 5c11893059 Add a link to the "bda-rappel" mail edition page 2017-06-02 19:33:23 +01:00
Martin Pépin 4fce1e8afc typo 2017-06-02 19:14:10 +01:00
Martin Pépin ffb777a4cd Test the "participants" views 2017-06-02 19:14:10 +01:00
Martin Pépin 76c75d5ccd Move bda-participant template -> bda/ folder 2017-06-02 19:14:10 +01:00
Martin Pépin fbdfdeef46 Add a link to the reminder emails sending page 2017-06-02 19:14:09 +01:00
Martin Pépin 31c034a96a send_rappel: use django messages + css tweaks 2017-06-02 19:14:06 +01:00
Martin Pépin 2a3614540f minor PEP8 changes 2017-06-02 17:32:23 +01:00
Martin Pépin 0e03fc85ee The BdA receives the reminder emails 2017-06-02 17:25:04 +01:00
Aurélien Delobelle 6e82a2cf88 minor fix 2017-05-31 22:00:51 +02:00
Aurélien Delobelle 5bed02c3fc Merge branch 'Kerl/banner' into 'master'
Add an announcement banner

Closes #151

See merge request !236
2017-05-30 23:35:08 +02:00
Aurélien Delobelle 8c6d56b27c Add Wagtail CMS for kfet app.
K-Fêt
- Integrate wagtail to serve "static" pages of old K-Fêt website
- Fixture "kfetcms/kfet_wagtail_17_05" contains a copy of old website
(as in May 2017).
- Media files can be got until end of June 17 at
http://partage.eleves.ens.fr//files/604e6dea2ceebc66b1936c6b3f911744/kfet_media.tar.gz

Login/logout
- Update package django_cas_ng to last version.
- Clean COFCASBackend.
- Change CAS version to 3 (version used on eleves.ens). This enables
the logout redirection (for CAS ofc).
- Add messages and clean existing ones on login/logout (for both
outsider and cas users).

Misc
- Update settings to bypass an incompability between debug-toolbar and
wagtailmenus packages.
- Better management of dev/test-specific urls (if debug-toolbar wasn't in
INSTALLED_APPS, media files were not served).
- UI improvements.
2017-05-30 20:44:30 +02:00
Martin Pépin ea21b07657 Add an announcement banner 2017-05-25 23:58:59 +01:00
Martin Pepin b13e992a30 Merge branch 'aureplop/clean_scroll' into 'master'
Misc UI

Closes #90 and #115

See merge request !225
2017-05-25 23:03:40 +02:00
Aurélien Delobelle 0815c96c1c Merge branch 'Kerl/postgres' into 'master'
Préparation au passage à postgres
- Suppression du champ num du modèle CofProfile.
- Suppression des LOCK (spécifique MySQL).
- Le code devient compatible avec tous les backends supportés par Django.
- Suppressions de code servant à la compatibilité python2.
- Corrige le message de succès à la fin de l'inscription. Celui-ci ne prenait pas en compte le statut is_cof à jour du profil.

See merge request !234
2017-05-23 22:50:10 +02:00
Martin Pépin 3a69c3371f Simple test: we can query the account-read page 2017-05-23 20:38:53 +01:00
Martin Pépin 74135f8877 enhance User.__str__ with get_full_name 2017-05-23 16:50:43 +01:00
Martin Pepin 92c946a286 Merge branch 'aureplop/clean_perms' into 'master'
Clean K-Fêt permissions

See merge request !235
2017-05-23 17:40:58 +02:00
Aurélien Delobelle 1d269ef4f9 Update migration to keep perms.
If content type for old kfet.GlobalPermissions exists:
- custom permissions related to this content type are updated to new content types,
- then we can safely remove this content type.
2017-05-23 16:48:33 +02:00
Aurélien Delobelle cd5a2e0591 Move K-Fêt global permissions. 2017-05-23 13:47:40 +02:00
Martin Pépin 3c8f1c58c5 Use transaction.atomic instead of lock_table
lock_table used LOCK which is mysql-specific
2017-05-23 05:58:09 +01:00
Martin Pépin f0f1585661 Registration: remove success var, fix message
`member.profile` was not up-to-date where we used to send the success
message => move it to the place where the success var is set tu `True`
and remove the success var was which becomes irrelevant.
2017-05-23 05:41:57 +01:00
Aurélien Delobelle a36d002b17 Merge branch 'Kerl/apt_upgrade' into 'master'
Upgrade apt packages while provisioning

See merge request !233
2017-05-22 15:08:16 +02:00
Martin Pépin 3762b38afe Remove useless pip upgrade in bootstrap.sh 2017-05-22 10:29:38 +01:00
Martin Pépin dba8a0a857 Remove the num field in CofProfile 2017-05-22 01:00:32 +01:00
Martin Pépin 76dcaf7d51 drop py2 compat 2017-05-21 23:58:44 +01:00
Martin Pépin 17b140fb12 Upgrade apt packages while provisioning 2017-05-21 18:42:30 +01:00
Aurélien Delobelle e60e347a53 Add base templates to extend
- kfet/base_col_1.html for one column content.
- kfet/base_col_2.html for two columns content (left for fixed content,
right for main content).
2017-05-20 13:57:21 +02:00
Aurélien Delobelle 1845ce825f jpp
Fixes #90
2017-05-19 20:13:36 +02:00
Aurélien Delobelle 109a692cea Order creation improvements.
- colspan takes into account the scale length.
- add box_capacity
2017-05-19 17:55:31 +02:00
Aurélien Delobelle c0b8430a12 Better scale management for order creation
+ Extends labels formatting of scale.
2017-05-19 17:40:06 +02:00
Aurélien Delobelle c11c3f84ed clean big tables forms 2017-05-19 16:42:26 +02:00
Aurélien Delobelle 4344b7d537 chrome support 2017-05-19 15:13:27 +02:00
Aurélien Delobelle 0995f712ca clean hover/focus/disabled btn 2017-05-19 14:48:57 +02:00
Aurélien Delobelle ecce2fda21 Merge branch 'master' into aureplop/clean_scroll 2017-05-19 14:08:57 +02:00
Aurélien Delobelle 51dca32d05 shorter transitions 2017-05-19 14:01:13 +02:00
Aurélien Delobelle ae27065626 Group permissions select multiple -> checkboxes
- Add handler for CheckboxSelectMultiple in form_field_snippet.html.
- Add template filter "widget_type" to get widget class name.
- Group permissions selection becomes easier.
2017-05-19 13:42:41 +02:00
Aurélien Delobelle e9073e2265 Improve multiple select inputs
+ Group edition form gains success message, is prettier
+ Fix: K-Fêt prefix for group name on this form
2017-05-18 21:41:23 +02:00
Aurélien Delobelle 1a661c1fd3 revert 2017-05-18 20:29:29 +02:00
Aurélien Delobelle 5776c81764 Clean navbar + content_center only
- Add css transitions on buttons + navbar links.
- Clean css of navbar.
- Clean templates with only centered form/content.
- Page opened when login as generic team user close itself (only for
  non-CAS users).
- A message is added when generic team user connects.
- Fix extra space on right when messages are prompted.
2017-05-18 20:17:05 +02:00
Aurélien Delobelle 95a8b484e0 Merge branch 'Aufinal/fix_history' into 'master'
Fix asynchronous calls with resetSettings

See merge request !229
2017-05-15 23:43:28 +02:00
Ludovic Stephan 647b32e727 Move displayAddcost function 2017-05-15 17:52:49 -03:00
Martin Pepin 6871945dcd Merge branch 'aureplop/fix_perms' into 'master'
Fix UserGroupForm

Closes #161

See merge request !228
2017-05-15 11:29:33 +02:00
Ludovic Stephan bf0e345301 Fix asynchronous calls with resetSettings
The `resetSettings` function now returns the ajax object, allowing to
chain other calls to it, e.g. the `getHistory` function.
2017-05-14 23:50:09 -03:00
Aurélien Delobelle e0b1db1e1e more robust tests 2017-05-14 22:19:25 +02:00
Martin Pepin 5d8427e601 Merge branch 'aureplop/fewer_requests' into 'master'
Moins de requêtes

See merge request !217
2017-05-12 21:13:35 +02:00
Aurélien Delobelle 4ac7b30bdd Fix UserGroupForm + tests for this form.
- Non-K-Fêt group membership is no longer erased by the account edit
  form.
- Add some tests to ensure proposed choices in this form corresponds to
  K-Fêt groups + test case for #161.

Fixes #161
2017-05-12 16:55:18 +02:00
Aurélien Delobelle b1e46792c8 (little) cleaning of order_create view 2017-05-10 13:11:47 +02:00
Aurélien Delobelle b0e7ebfbc5 fix typo + pep8 + del future imports 2017-05-10 12:49:14 +02:00
Aurélien Delobelle 6cdb791989 fix class name conflicts 2017-05-10 12:39:56 +02:00
Aurélien Delobelle e7266e7a9d use new settings for redis 2017-04-26 11:28:18 +02:00
Aurélien Delobelle 673dc0295d Merge branch 'master' into aureplop/fix_cache 2017-04-26 11:22:29 +02:00
Aurélien Delobelle 4ed5128829 Merge branch 'Kerl/settings2' into 'master'
Add settings for redis and fix settings handling in the provisioning script

See merge request !227
2017-04-26 11:19:44 +02:00
Martin Pépin fb4258f821 Set the redis passwd properly in bootstrap.sh 2017-04-25 20:23:51 +01:00
Martin Pépin dbff7740c8 Add REDIS_PORT to the settings and secrets 2017-04-25 20:23:21 +01:00
Ludovic Stephan 5c5fc6da1b Merge branch 'aureplop/less_prices_history' into 'master'
Less articles prices history

Closes #142

See merge request !224
2017-04-25 14:32:18 +02:00
Martin Pépin 52d9979d7c Fix settings in the provisioning script 2017-04-24 22:17:41 +01:00
Martin Pépin 2aee43e01a Add more configuration options for redis
- `REDIS_HOST` can be specified in the secrets
- Two new secrets: `REDIS_PASSWD` and `REDIS_DB`
2017-04-24 22:17:41 +01:00
Aurélien Delobelle 739990cdb6 Add total boxes to new inventory view + fix/clean
- Add total boxes in cellar and bar to new inventory view.
- On this view, table is "minified".
- Revert background color for some templates.
- Clean some margin (responsively).
- Clean tab pills on account read.
2017-04-22 01:17:23 +02:00
Aurélien Delobelle 6de0844d28 Merge branch 'master' into aureplop/clean_scroll 2017-04-21 22:28:22 +02:00
Aurélien Delobelle 0d8a613f28 improve bda inscription form/view code 2017-04-21 18:22:53 +02:00
Aurélien Delobelle 2eee8f58aa Merge branch 'master' into aureplop/fewer_requests 2017-04-17 20:45:01 +02:00
Aurélien Delobelle 8622002e8d minor change 2017-04-17 20:40:54 +02:00
Aurélien Delobelle dbf5844f6a Clean settings redis 2017-04-15 14:41:55 +02:00
Aurélien Delobelle e772d12721 Merge branch 'master' into aureplop/fix_cache 2017-04-15 14:27:20 +02:00
Aurélien Delobelle ce23eece6a Fix display on small screen devices.
- Remove useless margin on small screens.
- Better pills display on small screens.
- Revert to transparent background for section title.
2017-04-15 13:03:01 +02:00
Aurélien Delobelle ea81ab7b25 Few display improvements.
- Current day remains on the screen on history.
- Message for generic team user connection is sended only if user is
  connecting from a k-fet url.
- Less contrast on history.
2017-04-15 12:36:11 +02:00
Aurélien Delobelle 0a21858b33 Use css for scroll positionning
- Better rendering on scroll on pages with a left block
(- It removes the warning on Firefox about scroll positionning)
2017-04-14 13:08:03 +02:00
Aurélien Delobelle 7db497d095 Less articles prices history
- Prices given with order_to_inventory are saved to db only if they are
  updated (it doesn't create a new price row each time)

Fixes #142.
2017-04-13 16:34:29 +02:00
Aurélien Delobelle 06572f0bb5 Order create use Scale.
Order create view use WeekScale. No query improvements, only shorter
code.

Scale/ScaleMixin:
- Two methods directly relative to the Scale class move to... the Scale
  class.

- Fix order create on Chrome.
2017-04-13 14:11:44 +02:00
Aurélien Delobelle 3f4a1adbb9 Fewer queries on stats/scales + Fix
Scales:
- Fix #chunks when used with std_chunk=True (there was one too many at
  the beginning)
- Scale.end gives the end of the last chunk (instead of its start)
  So scale.begin -> scale.end gives the full range of the scale.

`kfet_day` now returns an aware datetime.

ScaleMixin:
- new method `get_by_chunks` which use only one query and ranks
  elements according to the scale. Elements are returned by a generator
  for each scale chunk (and all chunks are returned as a generator too).

ArticlesStatSales and AccountStatOperations use this new method to
avoid issuing #scale_chunks queries.

ArticleStat:
- fixed on Chrome
2017-04-12 18:03:31 +02:00
Aurélien Delobelle e97e0081d7 Fewer queries on stats of an account balance.
- Remove labels, should be replaced to an anchor to the relative
  operation in history.
- Add select_related as necessary.
2017-04-11 23:13:54 +02:00
Aurélien Delobelle b5cc26bb1b Merge branch 'master' into aureplop/fewer_requests 2017-04-11 22:46:18 +02:00
Aurélien Delobelle 2a3c892d39 Merge branch 'master' into aureplop/fix_cache 2017-04-10 21:39:54 +02:00
Aurélien Delobelle 36771c2c4f Use redis for cache.
- Cache use db #1 of redis.
- Channel layer (of channels) use db #0 of redis.
- `settings` try getting redis connection variables from environment.
- Drop memcached system
2017-04-10 21:36:00 +02:00
Aurélien Delobelle ab31c20649 missing CACHES value... 2017-04-10 19:47:00 +02:00
Aurélien Delobelle 2c40838938 Add real cache support
- Fix cache per process issue with a real cache system
- Configuration seems too easy... but it seems to work
2017-04-10 19:47:00 +02:00
Qwann be8d249ed7 remove useless code 2017-04-10 17:47:39 +02:00
Qwann e0b0a53112 stupidness removed 2017-04-10 17:18:43 +02:00
Qwann cb9ba76a4f small fixes 2017-04-10 16:47:13 +02:00
Qwann 5c6a73c597 kfet_open uses moment.js 2017-04-09 20:54:30 +02:00
Qwann 15873085e1 small fixes 2017-04-09 20:01:52 +02:00
Aurélien Delobelle 6ce2f178bf Fewer requests on petit cours details management. 2017-04-09 17:57:11 +02:00
Aurélien Delobelle 3dc91e30bd Fewer requests on petit cours list management. 2017-04-09 17:51:40 +02:00
Aurélien Delobelle 8870b5ace2 Fewer queries on poll view 2017-04-09 17:37:15 +02:00
Aurélien Delobelle ce70269e7b Fewer requests on create/update spectaclerevente in bda admin.
- O(#participant) -> O(1) requests
2017-04-08 17:50:36 +02:00
Aurélien Delobelle 0750551d7c Fewer requests on participant admin create and updateviews.
- Fewer requests on choicesreventes and tabular inlines (of
  attributions)
- User and tirage cannot be updated if updating a participant instance.
- Tabular inlines are fixed:
  - the one which is used for spectacles with listing only propose
    choices in spectacle with listing
  - same with the other (spectacles without listing)
- Still does too much request (because of tabularinlines)
2017-04-08 17:16:33 +02:00
Aurélien Delobelle 6451f971bd AttributionForm in bda admin
- New attribution form issue less queries
- Spectacle and Participant are readonly if updating an attribution.

ReadOnlyMixin allows to set readonly fields only while updating an
object.
2017-04-08 16:11:42 +02:00
Aurélien Delobelle bbe6f41962 Fewer requests on descriptions and catalogue views 2017-04-08 13:44:21 +02:00
Aurélien Delobelle 98f355ed20 Fewer requests on unpaid view 2017-04-08 13:08:53 +02:00
Aurélien Delobelle 15d2faf8e1 Fewer requests on spectacles and participants list views 2017-04-08 13:01:05 +02:00
Aurélien Delobelle 93a3a9af2c Fewer requests with shotgunable reventes
bda.views.revente_shotgun:
- 2 requests instead of ~#spectacles requests
2017-04-08 12:53:37 +02:00
Aurélien Delobelle fdc1128bd5 delete useless save 2017-04-08 12:12:56 +02:00
Aurélien Delobelle 3556e3b1b0 Fewer requests on bda.views.revente 2017-04-08 12:10:23 +02:00
Qwann e18c2c698c new migration 2017-04-07 17:52:44 +02:00
Qwann deb0d4de1e moving migration again 2017-04-07 17:23:41 +02:00
Qwann 531a4611d3 Merge branch 'master' into qwann/k-fet/kfet_open 2017-04-07 17:11:48 +02:00
Aurélien Delobelle d31101aff3 Empty SpectacleRevente and ChoixRevente before do_tirage.
- Usefull if a tirage is launched more than once.
- Ensure full reset of reventes for this tirage in this case.
2017-04-07 17:04:06 +02:00
Aurélien Delobelle 9f307c1bd0 Fewer db requests on bda tirage.
bda.algorithm
- use iterator to find max_groups, instead of a db request

bda.views.do_tirage
- select_related() are now focused on some relationships (they were
  taking useless relationships)
- bda-revente filling takes 1 request (each save and add was issuing
  1 request)
2017-04-07 16:22:10 +02:00
Aurélien Delobelle 3e0bd2e758 Fewer db requests on bda views.
bda.views.etat_places
- Use select_related on spectacles_set,  avoid query issue for each spectacle.
- `slots__sum` is computed in view, instead of a query.
- Cleaner code (and avoid useless computations).

bda.views.places
- Add select_related on places, avoid query issue for each spectacle.

bda.views.inscription
- 1 query for spectacle field choices, instead of (#forms in formset *
  #spectacles)
- Delete BaseBdaFormSet. The validation was redundant with
  `unique_together` of ChoixSpectacle model.
2017-04-07 13:25:50 +02:00
Aurélien Delobelle b8aa5d8bbe Merge branch 'master' into aureplop/fewer_requests 2017-04-06 21:25:22 +02:00
Aurélien Delobelle 0ed70eb0a7 PEP8
No improvement.
2017-04-06 20:30:23 +02:00
Aurélien Delobelle 026fba867d Fewer db requests on home view.
- 1 request instead of (2 + #articles)
2017-04-06 19:44:16 +02:00
Aurélien Delobelle 2731d4630f Fewer requests on accounts groups.
- Saves two queries.

(4 prefetch where done, replaced by 2 prefetchs (whose 1 with 2
selected_related))
2017-04-06 19:25:23 +02:00
Aurélien Delobelle afdb08b424 Fewer db requests with AccountNegative handling.
- AccountNegative use new AccountNegativeManager which select_related
  account, cofprofile and user for instances accessed via
  AccountNegative.objects.
- Compute sum of negatives with python instead of an SQL statement
  (since we already got the data with a previous query).
- Fix bug on account property `real_balance` (happened if
  an account has a relative AccountNegative instance but balance_offset
  to NULL).

- More compliant to PEP8
2017-04-06 19:07:13 +02:00
Aurélien Delobelle f8b71b604c Fewer db accesses on transfers list view.
- Add select_related for `from_acc`, `to_acc` and `group.valid_by`.
- 2 requests instead of (3 * #transfers)
2017-04-06 18:42:00 +02:00
Aurélien Delobelle 709d421b2c Add custom Manager for Account model.
- When Account model is queried with Account.objects, it always add
  .select_related('cofprofile_user', 'negative').
- Eg benefits: history doesn't do anymore one request by account to
  fill the account filter.

Important

Using this workaround (systemically append select_related) can be
dangerous normally, however a certain number of properties in
cofprofile and user are frequently used (as firstname or lastname), and
the benefits seems greater.
2017-04-06 18:23:27 +02:00
Aurélien Delobelle 33cee05884 initial checkout selected
Result is the same result but:
- auto-select go to javascript
- 1 less request for KPsulCheckoutForm

- delete debug messages
- some PEP8
2017-04-06 16:45:44 +02:00
Aurélien Delobelle 56fa70e495 fewer calls on account_read view 2017-04-06 14:38:25 +02:00
Aurélien Delobelle c9019c4eb4 Get debug toolbar with ajax calls
- Add django-debug-panel module to pip development dependencies
- Enable debug toolbar on ajax calls by using the "Django Debug Panel"
  extension for... Chrome.

For further informations about:
- django module, see https://github.com/recamshak/django-debug-panel
- Chrome extension, see https://github.com/recamshak/chrome-django-panel
2017-04-06 14:36:25 +02:00
Aurélien Delobelle 1302adf156 globally fewer db requests 2017-04-06 14:15:03 +02:00
Aurélien Delobelle f57bab8ae9 createopes use only bulk_create
createopes script:
- more than 6x faster
- only bulk_create is used instead of create or save
- correctly create editions (~5% of created operations)
- ratio of withdrawals go from ~10% to ~5%
2017-04-06 13:33:40 +02:00
Martin Pépin 387edd2fd5 Merge branch 'master' into Production 2017-04-05 23:33:20 +01:00
Martin Pépin 6f66fbbf3c Merge branch 'master' into Production 2017-04-05 22:39:30 +01:00
Qwann ef8fec89fe migration renamed 2017-04-05 17:50:49 +02:00
Qwann a5671fdf4c Merge branch 'master' into qwann/k-fet/kfet_open 2017-04-05 17:40:19 +02:00
Martin Pépin f6d43dffa1 exclude empty strings from ldap results
The uid attribute in a LDAP's entry cannot be an empty string. We need
to get an actual identifier.
2017-04-01 22:33:02 +01:00
Martin Pépin c3d740ade0 Handle incomplete values from the LDAP
Sometime `uid` is not set in the objects fetched from the LDAP. This
case has to be handled. Also, the `.uid` and `.cn` attributes of these
objects in the python abstractions have a `.value` method which we
should use.
2017-04-01 22:32:32 +01:00
Qwann b0643c0928 typo 2017-03-11 02:04:30 +01:00
Qwann 52dda7be9a Merge branch 'qwann/k-fet/home' into qwann/k-fet/kfet_open 2017-03-10 16:58:46 +01:00
Qwann cd31c55254 permission working 2017-03-10 16:40:36 +01:00
Qwann f18bb9f336 permission added 2017-03-09 17:27:58 +01:00
Qwann 8e7b9e2fd3 force close working 2017-03-09 15:05:47 +01:00
Qwann 5694e4b9bc migrations merged 2017-03-09 11:11:31 +01:00
Qwann ce96bc4b67 Status added on home page 2017-03-07 17:31:43 +01:00
Qwann f8e09cf257 unknow status updated every 30s 2017-03-07 14:12:23 +01:00
Qwann 0f96d3bc46 moving kfet_open url 2017-03-07 14:02:08 +01:00
Qwann 392338eddd indent 2017-03-06 09:30:58 +01:00
Qwann b7040d5867 adding KFET_FORCE_CLOSE in cache 2017-03-06 02:25:18 +01:00
Qwann 8e462134c7 home base html 2017-03-06 02:08:33 +01:00
Qwann 964dd716c6 Merge branch 'qwann/k-fet/home' into qwann/k-fet/kfet_open 2017-03-06 02:04:11 +01:00
Qwann 624707570e Merge branch 'qwann/k-fet/home' into qwann/k-fet/kfet_open 2017-03-05 20:06:37 +01:00
Qwann 15b8900d43 fixes dumb merge 2017-03-05 19:56:56 +01:00
Qwann a828ecbcf0 Merge branch 'qwann/k-fet/home' into qwann/k-fet/kfet_open 2017-03-05 19:51:51 +01:00
Qwann f87f1ceff1 kfetOpen bullet working 2017-02-11 00:29:12 +01:00
Qwann 4808650fa0 kfet_open is updatable 2017-02-09 14:05:29 +01:00
647 changed files with 84355 additions and 61308 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

12
.gitignore vendored
View file

@ -5,7 +5,19 @@ cof/settings.py
settings.py
*~
venv/
.venv/
.vagrant
/src
media/
*.log
.sass-cache/
*.sqlite3
.coverage
# PyCharm
.idea
.cache
# VSCode
.vscode/
.direnv

View file

@ -1,43 +1,95 @@
services:
- mysql:latest
- redis:latest
image: "python:3.7"
variables:
# GestioCOF settings
DJANGO_SETTINGS_MODULE: "cof.settings_dev"
DBNAME: "cof_gestion"
DBUSER: "cof_gestion"
DBPASSWD: "cof_password"
DBHOST: "mysql"
DBHOST: "postgres"
REDIS_HOST: "redis"
REDIS_PASSWD: "dummy"
# Cached packages
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
# mysql service configuration
MYSQL_DATABASE: "$DBNAME"
MYSQL_USER: "$DBUSER"
MYSQL_PASSWORD: "$DBPASSWD"
MYSQL_ROOT_PASSWORD: "root_password"
# postgres service configuration
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
POSTGRES_USER: "cof_gestion"
POSTGRES_DB: "cof_gestion"
# psql password authentication
PGPASSWORD: $POSTGRES_PASSWORD
cache:
paths:
- vendor/python
- vendor/pip
- vendor/apt
# apps to check migrations for
MIGRATION_APPS: "bda bds cofcms clubs events gestioncof kfet kfetauth kfetcms open petitscours shared"
before_script:
- mkdir -p vendor/{python,pip,apt}
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
-e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'"
# Remove the old test database if it has not been done yet
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
-e "DROP DATABASE test_$DBNAME" || true
- pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt
.test_template:
before_script:
- mkdir -p vendor/{pip,apt}
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' gestioasso/settings/secret_example.py > gestioasso/settings/secret.py
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' gestioasso/settings/secret.py
# Remove the old test database if it has not been done yet
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
- pip install --upgrade -r requirements-prod.txt coverage tblib
- python --version
after_script:
- coverage report
services:
- postgres:11.7
- redis:latest
cache:
key: test
paths:
- vendor/
# For GitLab CI to get coverage from build.
# Keep this disabled for now, as it may kill GitLab...
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
test:
coftest:
stage: test
extends: .test_template
variables:
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
script:
- python manage.py test
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
bdstest:
stage: test
extends: .test_template
variables:
DJANGO_SETTINGS_MODULE: "gestioasso.settings.bds_prod"
script:
- coverage run manage.py test bds clubs events --parallel
linters:
stage: test
before_script:
- mkdir -p vendor/pip
- pip install --upgrade black isort flake8
script:
- black --check .
- isort --check --diff .
# Print errors only
- flake8 --exit-zero bda bds clubs gestioasso events gestioncof kfet petitscours provisioning shared
cache:
key: linters
paths:
- vendor/
# Check whether there are some missing migrations.
migration_checks:
stage: test
variables:
DJANGO_SETTINGS_MODULE: "gestioasso.settings.local"
before_script:
- mkdir -p vendor/{pip,apt}
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
- cp gestioasso/settings/secret_example.py gestioasso/settings/secret.py
- pip install --upgrade -r requirements-devel.txt
- python --version
script: python manage.py makemigrations --dry-run --check $MIGRATION_APPS
services:
# this should not be necessary…
- postgres:11.7
cache:
key: migration_checks
paths:
- vendor/

106
.pre-commit.sh Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env bash
# pre-commit hook for gestioCOF project.
#
# Run formatters first, then checkers.
# Formatters which changed a file must set the flag 'formatter_updated'.
exit_code=0
formatter_updated=0
checker_dirty=0
# TODO(AD): We should check only staged changes.
# Working? -> Stash unstaged changes, run it, pop stash
STAGED_PYTHON_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".py$")
# Formatter: black
printf "> black ... "
if type black &>/dev/null; then
if [ -z "$STAGED_PYTHON_FILES" ]; then
printf "OK\n"
else
BLACK_OUTPUT="/tmp/gc-black-output.log"
touch $BLACK_OUTPUT
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black --check &>$BLACK_OUTPUT; then
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black &>$BLACK_OUTPUT
tail -1 $BLACK_OUTPUT
formatter_updated=1
else
printf "OK\n"
fi
fi
else
printf "SKIP: program not found\n"
printf "HINT: Install black with 'pip3 install black' (black requires Python>=3.6)\n"
fi
# Formatter: isort
printf "> isort ... "
if type isort &>/dev/null; then
if [ -z "$STAGED_PYTHON_FILES" ]; then
printf "OK\n"
else
ISORT_OUTPUT="/tmp/gc-isort-output.log"
touch $ISORT_OUTPUT
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort --check &>$ISORT_OUTPUT; then
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort &>$ISORT_OUTPUT
printf "Reformatted.\n"
formatter_updated=1
else
printf "OK\n"
fi
fi
else
printf "SKIP: program not found\n"
printf "HINT: Install isort with 'pip install isort'\n"
fi
# Checker: flake8
printf "> flake8 ... "
if type flake8 &>/dev/null; then
if [ -z "$STAGED_PYTHON_FILES" ]; then
printf "OK\n"
else
FLAKE8_OUTPUT="/tmp/gc-flake8-output.log"
touch $FLAKE8_OUTPUT
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' flake8 &>$FLAKE8_OUTPUT; then
printf "FAIL\n"
cat $FLAKE8_OUTPUT
checker_dirty=1
else
printf "OK\n"
fi
fi
else
printf "SKIP: program not found\n"
printf "HINT: Install flake8 with 'pip install flake8'\n"
fi
# End
if [ $checker_dirty -ne 0 ]
then
printf ">>> Checker(s) detect(s) issue(s)\n"
printf " You can still commit and push :)\n"
printf " Be warned that our CI may cause you more trouble.\n"
fi
if [ $formatter_updated -ne 0 ]
then
printf ">>> Working tree updated by formatter(s)\n"
printf " Add changes to staging area and retry.\n"
exit_code=1
fi
printf "\n"
exit $exit_code

298
CHANGELOG.md Normal file
View file

@ -0,0 +1,298 @@
# Changelog
Liste des changements notables dans GestioCOF depuis la version 0.1 (septembre
2018).
## Le FUTUR ! (pas prêt pour la prod)
### Nouveau module de gestion des événements
- Désormais complet niveau modèles
- Export des participants implémenté
#### TODO
- Vue de création d'événements ergonomique
- Vue d'inscription à un événement **ou** intégration propre dans la vue
"inscription d'un nouveau membre"
### Nouveau module de gestion des clubs
Uniquement un modèle simple de clubs avec des respos. Aucune gestion des
adhérents ni des cotisations.
## TODO Prod
- Créer un compte hCaptcha (https://www.hcaptcha.com/), au COF, et remplacer les secrets associés
## Version ??? - ??/??/????
## Version 0.15.1 - 15/06/2023
### K-Fêt
- Rattrape les erreurs d'envoi de mail de négatif
- Utilise l'adresse chefs pour les envois de négatifs
## Version 0.15 - 22/05/2023
### K-Fêt
- Rajoute un formulaire de contact
- Rajoute un formulaire de demande de soirée
- Désactive les mails d'envoi de négatifs sur les comptes gelés
## Version 0.14 - 19/05/2023
- Répare les dépendances en spécifiant toutes les versions
### K-Fêt
- Répare la gestion des changement d'heure via moment.js
## Version 0.13 - 19/02/2023
### K-Fêt
- Rajoute la valeur des inventaires
- Résout les problèmes de négatif ne disparaissant pas
- Affiche son surnom s'il y en a un
- Bugfixes
## Version 0.12.1 - 03/10/2022
### K-Fêt
- Fixe un problème de rendu causé par l'agrandissement du menu
## Version 0.12 - 17/06/2022
### K-Fêt
- Ajoute une exception à la limite d'historique pour les comptes `LIQ` et `#13`
- Répare le problème des étiquettes LIQ/Comptes K-Fêt inversées dans les stats des articles K-Fêt
## Version 0.11 - 26/10/2021
### COF
- Répare un problème de rendu sur le wagtail du COF
### K-Fêt
- Ajoute de mails de rappels pour les comptes en négatif
- La recherche de comptes sur K-Psul remarche normalement
- Le pointeur de la souris change de forme quand on survole un item d'autocomplétion
- Modification du gel de compte:
- on ne peut plus geler/dégeler son compte soi-même (il faut la permission "Gérer les permissions K-Fêt")
- on ne peut rien compter sur un compte gelé (aucune override possible), et les K-Fêteux·ses dont le compte est gelé perdent tout accès à K-Psul
- les comptes actuellement gelés (sur l'ancien système) sont dégelés automatiquement
- Modification du fonctionnement des négatifs
- impossible d'avoir des négatifs inférieurs à `kfet_config.overdraft_amount`
- il n'y a plus de limite de temps sur les négatifs
- supression des autorisations de négatif
- il n'est plus possible de réinitialiser la durée d'un négatif en faisant puis en annulant une charge
- La gestion des erreurs passe du client au serveur, ce qui permet d'avoir des messages plus explicites
- La supression d'opérations anciennes est réparée
## Version 0.10 - 18/04/2021
### K-Fêt
- On fait sauter la limite qui empêchait de vendre plus de 24 unités d'un item à
la fois.
- L'interface indique plus clairement quand on fait une erreur en modifiant un
compte.
- On supprime la fonction "décalage de balance".
- L'accès à l'historique est maintenant limité à 7 jours pour raison de
confidentialité. Les chefs/trez peuvent disposer d'une permission
supplémentaire pour accéder à jusqu'à 30 jours en cas de problème de compta.
L'accès à son historique personnel n'est pas limité. Les durées sont
configurables dans `settings/cof_prod.py`.
### COF
- Le Captcha sur la page de demande de petits cours utilise maintenant hCaptcha
au lieu de ReCaptcha, pour mieux respecter la vie privée des utilisateur·ices
## Version 0.9 - 06/02/2020
### COF / BdA
- Le COF peut remettre à zéro la liste de ses adhérents en août (sans passer par
KDE).
- La page d'accueil affiche la date de fermeture des tirages BdA.
- On peut revendre une place dès qu'on l'a payée, plus besoin de payer toutes
ses places pour pouvoir revendre.
- On s'assure que l'email fourni lors d'une demande de petit cours est valide.
### BDS
- Le burô peut maintenant accorder ou révoquer le statut de membre du Burô
en modifiant le profil d'un membre du BDS.
- Le burô peut exporter la liste de ses membres avec email au format CSV depuis
la page d'accueil.
### K-Fêt
- On affiche les articles actuellement en vente en premier lors des inventaires
et des commandes.
- On peut supprimer un inventaire. Seuls les articles dont c'est le dernier
inventaire sont affectés.
## Version 0.8 - 03/12/2020
### COF
- La page "Mes places" dans la section BdA indique quelles places sont sur
listing.
- ergonomie de l'interface admin du BdA : moins d'options inutiles lors de
la sélection de participants.
- les tirages sont maintenant archivables pour éviter d'avoir encore d'autres
options inutiles.
- l'autocomplétion dans l'admin BdA est réparée.
- Les icones de la page de gestion des petits cours sont (à nouveau) réparées.
- On a supprimé la possibilité de modifier les mails automatiques depuis
l'interface admin car trop problématique. Faute de mieux, envoyer un mail à
KDE pour modifier ces mails.
- corrige un crash sporadique sur la page d'inscription au système de petits
cours
### K-Fêt
- (fix partiel) Empêche la K-Fêt de modifier des données COF (e.g. nom, prénom,
username) lors de la création d'un nouveau compte.
- Les statistiques de conso globales montrent deux courbes COF / non-COF au
lieu de LIQ / sur compte.
- Un bug empêchait de fermer manuellement la K-Fêt depuis un compte non
privilégié en tapant un mot de passe. C'est corrigé.
## Version 0.7.2 - 08/09/2020
- Nouvelle page 404
- Correction de bug en K-Fêt : le lien pour créer un nouveau compte exté apparaît
à nouveau dans l'autocomplétion
## Version 0.7.1 - 05/09/2020
Petits ajustements sur le site du COF :
- Possibilité d'ajouter des champs d'infos supplémentaires en plus de l'email et
de la page web dans les annuaires (clubs et partenaires).
- Corrige un bug d'affichage des adresses emails de clubs
## Version 0.7 - 29/08/2020
### GestioBDS
- Ajout d'un bouton pour supprimer un compte
- Le nombre d'adhérent⋅es est affiché sur la page d'accueil
- le groupe BDS a les bonnes permissions
### Site du COF
- Captcha fonctionnel pour les mailing-listes
### K-Fêt
- L'autocomplétion pour la création de compte K-Fêt se lance à 3 caractères seulement,
donc est plus rapide.
## Version 0.6 - 27/07/2020
Arrivée du BDS !
GestioCOF et GestioBDS ont du code en commun mais tournent de façon séparée, les
deux bases de données sont distinctes.
## Version 0.5 - 11/07/2020
### Problèmes corrigés
- La recherche d'utilisateurices (COF + K-Fêt) fonctionne de nouveau
- Bug d'affichage quand on a beaucoup de clubs dans le cadre "Accès rapide" sur
la page des clubs (nouveau site du COF)
- Version mobile plus ergonimique sur le nouveau site du COF
- Cliquer sur "visualiser" sur les pages de clubs dans wagtail ne provoque plus
d'erreurs 500 (nouveau site du COF)
- L'historique des ventes des articles K-Fêt fonctionne à nouveau
- Les montants en K-Fêt sont à nouveau affichés en UKF (et non en €).
- Les boutons "afficher/cacher" des mails et noms des participant⋅e⋅s à un
spectacle BdA fonctionnent à nouveau.
- on ne peut plus compter de consos sur ☠☠☠, ni éditer les comptes spéciaux
(LIQ, GNR, ☠☠☠, #13).
### Nouvelles fonctionnalités
- On n'affiche que 4 articles sur la pages "nouveautés" (nouveau site du COF)
- Plus de traductions sur le nouveau site du COF
- Les transferts apparaissent maintenant dans l'historique K-Fêt et l'historique
personnel.
- les statistiques K-Fêt remontent à plus d'un an (et le code est simplifié)
## Version 0.4.1 - 17/01/2020
- Corrige un bug sur K-Psul lorsqu'un trigramme contient des caractères réservés
aux urls (\#, /...)
## Version 0.4 - 15/01/2020
- Corrige un bug d'affichage d'images sur l'interface des petits cours
- La page des transferts permet de créer un nombre illimité de transferts en
une fois.
- Nouveau site du COF : les liens sont optionnels dans les descriptions de clubs
- Mise à jour du lien vers le calendire de la K-Fêt sur la page d'accueil
- Certaines opérations sont à nouveau accessibles depuis la session partagée
K-Fêt.
- Le bouton "déconnexion" déconnecte vraiment du CAS pour les comptes clipper
- Corrige un crash sur la page des reventes pour les nouveaux participants.
- Corrige un bug d'affichage pour les trigrammes avec caractères spéciaux
## Version 0.3.3 - 30/11/2019
- Corrige un problème de redirection lors de la déconnexion (CAS seulement)
- Les catégories d'articles K-Fêt peuvent être exemptées de subvention COF
- Corrige un bug d'affichage dans K-Psul quand on annule une transaction sur LIQ
- Corrige une privilege escalation liée aux sessions partagées en K-Fêt
https://git.eleves.ens.fr/klub-dev-ens/gestioCOF/issues/240
## Version 0.3.2 - 04/11/2019
- Bugfix: modifier un compte K-Fêt ne supprime plus nom/prénom
## Version 0.3.1 - 19/10/2019
- Bugfix: l'historique des utilisateurices s'affiche à nouveau
## Version 0.3 - 16/10/2019
- Comptes extés: lien pour changer son mot de passe sur la page d'accueil
- Les utilisateurices non-COF peuvent éditer leur profil
- Un peu de pub pour KDEns sur la page d'accueil
- Fix erreur 500 sur /bda/revente/<tirage_id>/manage
- Si on essaie d'accéder au compte que qqn d'autre on a une 404 (et plus une 403)
- On ne peut plus modifier des comptes COF depuis l'interface K-Fêt
- Le champ de paiement BdA se fait au niveau des attributions
- Affiche un message d'erreur plutôt que de crasher si échec de l'envoi du mail
de bienvenue aux nouveaux membres
- On peut supprimer des comptes et des articles K-Fêt
- Passage à Django2
- Dev : on peut désactiver la barre de debug avec une variable shell
- Remplace les CSS de Google par des polices de proximité
- Passage du site du COF et de la K-Fêt en Wagtail 2.3 et Wagtail-modeltranslation 0.9
- Ajoute un lien vers l'administration générale depuis les petits cours
- Abandon de l'ancien catalogue BdA (déjà plus utilisé depuis longtemps)
- Force l'unicité des logins clipper
- Nouveau site du COF en wagtail
- Meilleurs affichage des longues listes de spectacles à cocher dans BdA-Revente
- Bugfix : les pages de la revente ne sont plus accessibles qu'aux membres du
COF
## Version 0.2 - 07/11/2018
- Corrections de bugs d'interface dans l'inscription aux tirages BdA
- On peut annuler une revente à tout moment
- Pleiiiiin de tests
## Version 0.1 - 09/09/2018
Début de la numérotation des versions, début du changelog

242
README.md
View file

@ -1,17 +1,86 @@
# GestioCOF
# GestioCOF / GestioBDS
[![pipeline status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/pipeline.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
[![coverage report](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/coverage.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
## Installation
Il est possible d'installer GestioCOF sur votre machine de deux façons différentes :
- L'[installation manuelle](#installation-manuelle) (**recommandée** sous linux et OSX), plus légère
- L'[installation via vagrant](#vagrant) qui fonctionne aussi sous windows mais un peu plus lourde
### Installation manuelle
Il est fortement conseillé d'utiliser un environnement virtuel pour Python.
Il vous faudra installer pip, les librairies de développement de python ainsi
que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
Debian et dérivées (Ubuntu, ...) :
sudo apt-get install python3-pip python3-dev python3-venv sqlite3
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
(le dossier où se trouve ce README), et créez-le maintenant :
python3 -m venv venv
Pour l'activer, il faut taper
. venv/bin/activate
depuis le même dossier.
Vous pouvez maintenant installer les dépendances Python depuis le fichier
`requirements-devel.txt` :
pip install -U pip # parfois nécessaire la première fois
pip install -r requirements-devel.txt
Pour terminer, copier le fichier `gestioasso/settings/secret_example.py` vers
`gestioasso/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
pour profiter de façon transparente des mises à jour du fichier:
ln -s secret_example.py gestioasso/settings/secret.py
Nous avons un git hook de pre-commit pour formatter et vérifier que votre code
vérifie nos conventions. Pour bénéficier des mises à jour du hook, préférez
encore l'installation *via* un lien symbolique:
ln -s ../../.pre-commit.sh .git/hooks/pre-commit
Pour plus d'informations à ce sujet, consulter la
[page](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/coding-style)
du wiki gestioCOF liée aux conventions.
#### Fin d'installation
Il ne vous reste plus qu'à initialiser les modèles de Django et peupler la base
de donnée avec les données nécessaires au bon fonctionnement de GestioCOF + des
données bidons bien pratiques pour développer avec la commande suivante :
bash provisioning/prepare_django.sh
Voir le paragraphe ["outils pour développer"](#outils-pour-d-velopper) plus bas
pour plus de détails.
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
python manage.py runserver
### Vagrant
La façon recommandée d'installer GestioCOF sur votre machine est d'utiliser
Une autre façon d'installer GestioCOF sur votre machine est d'utiliser
[Vagrant](https://www.vagrantup.com/). Vagrant permet de créer une machine
virtuelle minimale sur laquelle tournera GestioCOF; ainsi on s'assure que tout
le monde à la même configuration de développement (même sous Windows !), et
l'installation se fait en une commande.
Pour utiliser Vagrant, il faut le
[télécharger](https://www.vagrantup.com/downloads.html) et l'installer.
[télécharger](https://www.vagrantup.com/downloads.html) et l'installer.
Si vous êtes sous Linux, votre distribution propose probablement des paquets
Vagrant dans le gestionnaire de paquets (la version sera moins récente, ce qui
@ -66,137 +135,72 @@ car par défaut Django n'écoute que sur l'adresse locale de la machine virtuell
or vous voudrez accéder à GestioCOF depuis votre machine physique. L'url à
entrer dans le navigateur est `localhost:8000`.
#### Serveur de développement type production
Sur la VM Vagrant, un serveur apache est configuré pour servir GestioCOF de
façon similaire à la version en production : on utilise
Juste histoire de jouer, pas indispensable pour développer :
La VM Vagrant héberge en plus un serveur nginx configuré pour servir GestioCOF
comme en production : on utilise
[Daphne](https://github.com/django/daphne/) et `python manage.py runworker`
derrière un reverse-proxy apache. Le tout est monitoré par
[supervisor](http://supervisord.org/).
derrière un reverse-proxy nginx.
Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
`localhost:8080`. Toutefois il ne se recharge pas tout seul lorsque le code
change, il faut relancer le worker avec `sudo supervisorctl restart worker` pour
visualiser la dernière version du code.
### Installation manuelle
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
est fortement conseillé d'utiliser un environnement virtuel pour Python.
Il vous faudra installer pip, les librairies de développement de python, un
client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et dérivées
(Ubuntu, ...) :
sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
(le dossier où se trouve ce README), et créez-le maintenant :
virtualenv env -p $(which python3)
L'option `-p` sert à préciser l'exécutable python à utiliser. Vous devez choisir
python3, si c'est la version de python par défaut sur votre système, ceci n'est
pas nécessaire. Pour l'activer, il faut faire
. env/bin/activate
dans le même dossier.
Vous pouvez maintenant installer les dépendances Python depuis le fichier
`requirements-devel.txt` :
pip install -r requirements-devel.txt
Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
#### Installation avec MySQL
Il faut maintenant installer MySQL. Si vous n'avez pas déjà MySQL installé sur
votre serveur, il faut l'installer ; sous Debian et dérivées (Ubuntu, ...) :
sudo apt-get install mysql-server
Il vous demandera un mot de passe pour le compte d'administration MySQL,
notez-le quelque part (ou n'en mettez pas, le serveur n'est accessible que
localement par défaut). Si vous utilisez une autre distribution, consultez la
documentation de votre distribution pour savoir comment changer ce mot de passe
et démarrer le serveur MySQL (c'est automatique sous Ubuntu).
Vous devez alors créer un utilisateur local et une base `cof_gestion`, avec le
mot de passe de votre choix (remplacez `mot_de_passe`) :
mysql -uroot -e "CREATE DATABASE cof_gestion; GRANT ALL PRIVILEGES ON cof_gestion.* TO 'cof_gestion'@'localhost' IDENTIFIER BY 'mot_de_passe'"
Éditez maintenant le fichier `cof/settings.py` pour y intégrer ces changements ;
la définition de `DATABASES` doit ressembler à (à nouveau, remplacez
`mot_de_passe` de façon appropriée) :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cof_gestion',
'USER': 'cof_gestion',
'PASSWORD': 'mot_de_passe',
}
}
#### Installation avec SQLite
GestioCOF est installé avec MySQL sur la VM COF, et afin d'avoir un
environnement de développement aussi proche que possible de ce qui tourne en
vrai pour éviter les mauvaises surprises, il est conseillé d'utiliser MySQL sur
votre machine de développement également. Toutefois, GestioCOF devrait
fonctionner avec d'autres moteurs SQL, et certains préfèrent utiliser SQLite
pour sa légèreté et facilité d'installation.
Si vous décidez d'utiliser SQLite, il faut l'installer ; sous Debian et dérivées :
sudo apt-get install sqlite3
puis éditer le fichier `cof/settings.py` pour que la définition de `DATABASES`
ressemble à :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
#### Fin d'installation
Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande suivante :
python manage.py migrate
Charger les mails indispensables au bon fonctionnement de GestioCOF :
python manage.py syncmails
Une base de donnée pré-remplie est disponible en lançant les commandes :
python manage.py loaddata gestion sites accounts groups articles
python manage.py loaddevdata
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
python manage.py runserver
`localhost:8080/gestion/`. Toutefois il ne se recharge pas tout seul lorsque le
code change, il faut relancer le worker avec `sudo systemctl restart
worker.service` pour visualiser la dernière version du code.
### Mise à jour
Pour mettre à jour les paquets Python, utiliser la commande suivante :
pip install --upgrade -r requirements.txt -r requirements-devel.txt
pip install --upgrade -r requirements-devel.txt
Pour mettre à jour les modèles après une migration, il faut ensuite faire :
python manage.py migrate
## Outils pour développer
### Base de donnée
Quelle que soit la méthode d'installation choisie, la base de donnée locale est
peuplée avec des données artificielles pour faciliter le développement.
- Un compte `root` (mot de passe `root`) avec tous les accès est créé. Connectez
vous sur ce compte pour accéder à tout GestioCOF.
- Des comptes utilisateurs COF et non-COF sont créés ainsi que quelques
spectacles BdA et deux tirages au sort pour jouer avec les fonctionnalités du BdA.
- À chaque compte est associé un trigramme K-Fêt
- Un certain nombre d'articles K-Fêt sont renseignés.
### Tests unitaires
On écrit désormais des tests unitaires qui sont lancés automatiquement sur gitlab
à chaque push. Il est conseillé de lancer les tests sur sa machine avant de proposer un patch pour s'assurer qu'on ne casse pas une fonctionnalité existante.
Pour lancer les tests :
```
python manage.py test
```
### Astuces
- En développement on utilise la django debug toolbar parce que c'est utile pour
débuguer les templates ou les requêtes SQL mais des fois c'est pénible parce
ça fait ramer GestioCOF (surtout dans wagtail).
Vous pouvez la désactiver temporairement en définissant la variable
d'environnement `DJANGO_NO_DDT` dans votre shell : par exemple dans
bash/zsh/…:
```
$ export DJANGO_NO_DDT=1
```
## Documentation utilisateur
Une brève documentation utilisateur pour se familiariser plus vite avec l'outil
est accessible sur le
[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home).
Une brève documentation utilisateur est accessible sur le
[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home) pour avoir une
idée de la façon dont le COF utilise GestioCOF.

42
Vagrantfile vendored
View file

@ -1,47 +1,19 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
# Configuration de base pour GestioCOF.
# Voir https://docs.vagrantup.com pour plus d'informations.
Vagrant.configure(2) do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
config.vm.box = "ubuntu/xenial64"
# On se base sur Debian 10 (Buster) pour avoir le même environnement qu'en
# production.
config.vm.box = "debian/contrib-buster64"
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
# ordinateur, et le port 8000 avec le port 8000.
config.vm.network :forwarded_port, guest: 80, host: 8080
config.vm.network :forwarded_port, guest: 8000, host: 8000
# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# sudo apt-get update
# sudo apt-get install -y apache2
# SHELL
# Le restes de la configuration (installation de paquets, etc) est géré un
# script shell.
config.vm.provision :shell, path: "provisioning/bootstrap.sh"
end

View file

@ -1,234 +0,0 @@
# -*- coding: utf-8 -*-
import autocomplete_light
from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail
from django.contrib import admin
from django.db.models import Sum, Count
from django.template.defaultfilters import pluralize
from django.utils import timezone
from django import forms
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
sortable_field_name = "priority"
class AttributionInline(admin.TabularInline):
model = Attribution
extra = 0
def get_queryset(self, request):
qs = super(AttributionInline, self).get_queryset(request)
return qs.filter(spectacle__listing=False)
class AttributionInlineListing(admin.TabularInline):
model = Attribution
exclude = ('given', )
extra = 0
def get_queryset(self, request):
qs = super(AttributionInlineListing, self).get_queryset(request)
return qs.filter(spectacle__listing=True)
class ParticipantAdmin(admin.ModelAdmin):
inlines = [AttributionInline, AttributionInlineListing]
def get_queryset(self, request):
return Participant.objects.annotate(nb_places=Count('attributions'),
total=Sum('attributions__price'))
def nb_places(self, obj):
return obj.nb_places
nb_places.admin_order_field = "nb_places"
nb_places.short_description = "Nombre de places"
def total(self, obj):
tot = obj.total
if tot:
return "%.02f" % tot
else:
return "0 €"
total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
"tirage")
list_filter = ("paid", "tirage")
search_fields = ('user__username', 'user__first_name', 'user__last_name')
actions = ['send_attribs', ]
actions_on_bottom = True
list_per_page = 400
readonly_fields = ("total",)
def send_attribs(self, request, queryset):
datatuple = []
for member in queryset.all():
attribs = member.attributions.all()
context = {'member': member.user}
shortname = ""
if len(attribs) == 0:
shortname = "bda-attributions-decus"
else:
shortname = "bda-attributions"
context['places'] = attribs
print(context)
datatuple.append((shortname, context, "bda@ens.fr",
[member.user.email]))
send_mass_custom_mail(datatuple)
count = len(queryset.all())
if count == 1:
message_bit = "1 membre a"
plural = ""
else:
message_bit = "%d membres ont" % count
plural = "s"
self.message_user(request, "%s été informé%s avec succès."
% (message_bit, plural))
send_attribs.short_description = "Envoyer les résultats par mail"
class AttributionAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = super(AttributionAdminForm, self).clean()
participant = cleaned_data.get("participant")
spectacle = cleaned_data.get("spectacle")
if participant and spectacle:
if participant.tirage != spectacle.tirage:
raise forms.ValidationError(
"Erreur : le participant et le spectacle n'appartiennent"
"pas au même tirage")
return cleaned_data
class AttributionAdmin(admin.ModelAdmin):
def paid(self, obj):
return obj.participant.paid
paid.short_description = 'A payé'
paid.boolean = True
list_display = ("id", "spectacle", "participant", "given", "paid")
search_fields = ('spectacle__title', 'participant__user__username',
'participant__user__first_name',
'participant__user__last_name')
form = AttributionAdminForm
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
def tirage(self, obj):
return obj.participant.tirage
list_display = ("participant", "tirage", "spectacle", "priority",
"double_choice")
list_filter = ("double_choice", "participant__tirage")
search_fields = ('participant__user__username',
'participant__user__first_name',
'participant__user__last_name',
'spectacle__title')
class QuoteInline(admin.TabularInline):
model = Quote
class SpectacleAdmin(admin.ModelAdmin):
inlines = [QuoteInline]
model = Spectacle
list_display = ("title", "date", "tirage", "location", "slots", "price",
"listing")
list_filter = ("location", "tirage",)
search_fields = ("title", "location__name")
readonly_fields = ("rappel_sent", )
class TirageAdmin(admin.ModelAdmin):
model = Tirage
list_display = ("title", "ouverture", "fermeture", "active",
"enable_do_tirage")
readonly_fields = ("tokens", )
list_filter = ("active", )
search_fields = ("title", )
class SalleAdmin(admin.ModelAdmin):
model = Salle
search_fields = ('name', 'address')
class SpectacleReventeAdmin(admin.ModelAdmin):
"""
Administration des reventes de spectacles
"""
model = SpectacleRevente
def spectacle(self, obj):
"""
Raccourci vers le spectacle associé à la revente.
"""
return obj.attribution.spectacle
list_display = ("spectacle", "seller", "date", "soldTo")
raw_id_fields = ("attribution",)
readonly_fields = ("date_tirage",)
search_fields = ['attribution__spectacle__title',
'seller__user__username',
'seller__user__first_name',
'seller__user__last_name']
actions = ['transfer', 'reinit']
actions_on_bottom = True
def transfer(self, request, queryset):
"""
Effectue le transfert des reventes pour lesquels on connaît l'acheteur.
"""
reventes = queryset.exclude(soldTo__isnull=True).all()
count = reventes.count()
for revente in reventes:
attrib = revente.attribution
attrib.participant = revente.soldTo
attrib.save()
self.message_user(
request,
"%d attribution%s %s été transférée%s avec succès." % (
count, pluralize(count),
pluralize(count, "a,ont"), pluralize(count))
)
transfer.short_description = "Transférer les reventes sélectionnées"
def reinit(self, request, queryset):
"""
Réinitialise les reventes.
"""
count = queryset.count()
for revente in queryset.filter(
attribution__spectacle__date__gte=timezone.now()):
revente.date = timezone.now() - timedelta(hours=1)
revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
if revente.answered_mail:
revente.answered_mail.clear()
revente.save()
self.message_user(
request,
"%d attribution%s %s été réinitialisée%s avec succès." % (
count, pluralize(count),
pluralize(count, "a,ont"), pluralize(count))
)
reinit.short_description = "Réinitialiser les reventes sélectionnées"
admin.site.register(CategorieSpectacle)
admin.site.register(Spectacle, SpectacleAdmin)
admin.site.register(Salle, SalleAdmin)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
admin.site.register(Tirage, TirageAdmin)
admin.site.register(SpectacleRevente, SpectacleReventeAdmin)

View file

@ -1,107 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django.db.models import Max
import random
class Algorithm(object):
shows = None
ranks = None
origranks = None
double = None
def __init__(self, shows, members, choices):
"""Initialisation :
- on aggrège toutes les demandes pour chaque spectacle dans
show.requests
- on crée des tables de demandes pour chaque personne, afin de
pouvoir modifier les rankings"""
self.max_group = \
2 * choices.aggregate(Max('priority'))['priority__max']
self.shows = []
showdict = {}
for show in shows:
show.nrequests = 0
showdict[show] = show
show.requests = []
self.shows.append(show)
self.ranks = {}
self.origranks = {}
self.choices = {}
next_rank = {}
member_shows = {}
for member in members:
self.ranks[member] = {}
self.choices[member] = {}
next_rank[member] = 1
member_shows[member] = {}
for choice in choices:
member = choice.participant
if choice.spectacle in member_shows[member]:
continue
else:
member_shows[member][choice.spectacle] = True
showdict[choice.spectacle].requests.append(member)
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
self.ranks[member][choice.spectacle] = next_rank[member]
next_rank[member] += 2 if choice.double else 1
self.choices[member][choice.spectacle] = choice
for member in members:
self.origranks[member] = dict(self.ranks[member])
def IncrementRanks(self, member, currank, increment=1):
for show in self.ranks[member]:
if self.ranks[member][show] > currank:
self.ranks[member][show] -= increment
def appendResult(self, l, member, show):
l.append((member,
self.ranks[member][show],
self.origranks[member][show],
self.choices[member][show].double))
def __call__(self, seed):
random.seed(seed)
results = []
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
reverse=True)
for show in shows:
# On regroupe tous les gens ayant le même rang
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
for member in show.requests:
if self.ranks[member][show] == 0:
raise RuntimeError(member, show.title)
groups[self.ranks[member][show]].append(member)
# On passe à l'attribution
winners = []
losers = []
for i in range(1, self.max_group + 1):
group = list(groups[i])
random.shuffle(group)
for member in group:
if self.choices[member][show].double: # double
if len(winners) + 1 < show.slots:
self.appendResult(winners, member, show)
self.appendResult(winners, member, show)
elif not self.choices[member][show].autoquit \
and len(winners) < show.slots:
self.appendResult(winners, member, show)
self.appendResult(losers, member, show)
else:
self.appendResult(losers, member, show)
self.appendResult(losers, member, show)
self.IncrementRanks(member, i, 2)
else: # simple
if len(winners) < show.slots:
self.appendResult(winners, member, show)
else:
self.appendResult(losers, member, show)
self.IncrementRanks(member, i)
results.append((show, winners, losers))
return results

View file

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(
Participant, search_fields=('user__username', 'user__first_name',
'user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(
Spectacle, search_fields=('title', ),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

View file

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django import forms
from django.forms.models import BaseInlineFormSet
from django.utils import timezone
from bda.models import Attribution, Spectacle
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
super(BaseBdaFormSet, self).clean()
if any(self.errors):
# Don't bother validating the formset unless each form is valid on
# its own
return
spectacles = []
for i in range(0, self.total_form_count()):
form = self.forms[i]
if not form.cleaned_data:
continue
spectacle = form.cleaned_data['spectacle']
delete = form.cleaned_data['DELETE']
if not delete and spectacle in spectacles:
raise forms.ValidationError(
"Vous ne pouvez pas vous inscrire deux fois pour le "
"même spectacle.")
spectacles.append(spectacle)
class TokenForm(forms.Form):
token = forms.CharField(widget=forms.widgets.Textarea())
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return "%s" % obj.spectacle
class ResellForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
queryset=Attribution.objects.none(),
widget=forms.CheckboxSelectMultiple,
required=False)
def __init__(self, participant, *args, **kwargs):
super(ResellForm, self).__init__(*args, **kwargs)
self.fields['attributions'].queryset = participant.attribution_set\
.filter(spectacle__date__gte=timezone.now())\
.exclude(revente__seller=participant)
class AnnulForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
queryset=Attribution.objects.none(),
widget=forms.CheckboxSelectMultiple,
required=False)
def __init__(self, participant, *args, **kwargs):
super(AnnulForm, self).__init__(*args, **kwargs)
self.fields['attributions'].queryset = participant.attribution_set\
.filter(spectacle__date__gte=timezone.now(),
revente__isnull=False,
revente__notif_sent=False,
revente__soldTo__isnull=True)
class InscriptionReventeForm(forms.Form):
spectacles = forms.ModelMultipleChoiceField(
queryset=Spectacle.objects.none(),
widget=forms.CheckboxSelectMultiple,
required=False)
def __init__(self, tirage, *args, **kwargs):
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
date__gte=timezone.now())
class SoldForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
queryset=Attribution.objects.none(),
widget=forms.CheckboxSelectMultiple)
def __init__(self, participant, *args, **kwargs):
super(SoldForm, self).__init__(*args, **kwargs)
self.fields['attributions'].queryset = (
participant.attribution_set
.filter(revente__isnull=False,
revente__soldTo__isnull=False)
.exclude(revente__soldTo=participant)
)

View file

@ -1,107 +0,0 @@
"""
Crée deux tirages de test et y inscrit les utilisateurs
"""
import os
import random
from django.utils import timezone
from django.contrib.auth.models import User
from gestioncof.management.base import MyBaseCommand
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
from bda.views import do_tirage
# Où sont stockés les fichiers json
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
'data')
class Command(MyBaseCommand):
help = "Crée deux tirages de test et y inscrit les utilisateurs."
def handle(self, *args, **options):
# ---
# Tirages
# ---
Tirage.objects.all().delete()
Tirage.objects.bulk_create([
Tirage(
title="Tirage de test 1",
ouverture=timezone.now()-timezone.timedelta(days=7),
fermeture=timezone.now(),
active=True
),
Tirage(
title="Tirage de test 2",
ouverture=timezone.now(),
fermeture=timezone.now()+timezone.timedelta(days=60),
active=True
)
])
tirages = Tirage.objects.all()
# ---
# Salles
# ---
locations = self.from_json('locations.json', DATA_DIR, Salle)
# ---
# Spectacles
# ---
def show_callback(show):
"""
Assigne un tirage, une date et un lieu à un spectacle et décide si
les places sont sur listing.
"""
show.tirage = random.choice(tirages)
show.listing = bool(random.randint(0, 1))
show.date = (
show.tirage.fermeture
+ timezone.timedelta(days=random.randint(60, 90))
)
show.location = random.choice(locations)
return show
shows = self.from_json(
'shows.json', DATA_DIR, Spectacle, show_callback
)
# ---
# Inscriptions
# ---
self.stdout.write("Inscription des utilisateurs aux tirages")
ChoixSpectacle.objects.all().delete()
choices = []
for user in User.objects.filter(profile__is_cof=True):
for tirage in tirages:
part, _ = Participant.objects.get_or_create(
user=user,
tirage=tirage
)
shows = random.sample(
list(tirage.spectacle_set.all()),
tirage.spectacle_set.count() // 2
)
for (rank, show) in enumerate(shows):
choices.append(ChoixSpectacle(
participant=part,
spectacle=show,
priority=rank + 1,
double_choice=random.choice(
['1', 'double', 'autoquit']
)
))
ChoixSpectacle.objects.bulk_create(choices)
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
# ---
# On lance le premier tirage
# ---
self.stdout.write("Lancement du premier tirage")
do_tirage(tirages[0], "dummy_token")

View file

@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
"""
Gestion en ligne de commande des reventes.
"""
from __future__ import unicode_literals
from datetime import timedelta
from django.core.management import BaseCommand
from django.utils import timezone
from bda.models import SpectacleRevente
class Command(BaseCommand):
help = "Envoie les mails de notification et effectue " \
"les tirages au sort des reventes"
leave_locale_alone = True
def handle(self, *args, **options):
now = timezone.now()
reventes = SpectacleRevente.objects.all()
for revente in reventes:
# Check si < 24h
if (revente.attribution.spectacle.date <=
revente.date + timedelta(days=1)) and \
now >= revente.date + timedelta(minutes=15) and \
not revente.notif_sent:
self.stdout.write(str(now))
revente.mail_shotgun()
self.stdout.write("Mail de disponibilité immédiate envoyé")
# Check si délai de retrait dépassé
elif (now >= revente.date + timedelta(hours=1) and
not revente.notif_sent):
self.stdout.write(str(now))
revente.send_notif()
self.stdout.write("Mail d'inscription à une revente envoyé")
# Check si tirage à faire
elif (now >= revente.date_tirage and
not revente.tirage_done):
self.stdout.write(str(now))
revente.tirage()
self.stdout.write("Tirage effectué, mails envoyés")

View file

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
"""
Gestion en ligne de commande des mails de rappel.
"""
from __future__ import unicode_literals
from datetime import timedelta
from django.core.management.base import BaseCommand
from django.utils import timezone
from bda.models import Spectacle
class Command(BaseCommand):
help = 'Envoie les mails de rappel des spectacles dont la date ' \
'approche.\nNe renvoie pas les mails déjà envoyés.'
leave_locale_alone = True
def handle(self, *args, **options):
now = timezone.now()
delay = timedelta(days=4)
shows = Spectacle.objects \
.filter(date__range=(now, now+delay)) \
.filter(tirage__active=True) \
.filter(rappel_sent__isnull=True) \
.all()
for show in shows:
show.send_rappel()
self.stdout.write(
'Mails de rappels pour %s envoyés avec succès.' % show)
if not shows:
self.stdout.write('Aucun mail à envoyer.')

View file

@ -1,26 +0,0 @@
[
{
"name": "Cour\u00f4",
"address": "45 rue d'Ulm, cour\u00f4"
},
{
"name": "K-F\u00eat",
"address": "45 rue d'Ulm, escalier C, niveau -1"
},
{
"name": "Th\u00e9\u00e2tre",
"address": "45 rue d'Ulm, escalier C, niveau -1"
},
{
"name": "Cours Pasteur",
"address": "45 rue d'Ulm, cours pasteur"
},
{
"name": "Salle des actes",
"address": "45 rue d'Ulm, escalier A, niveau 1"
},
{
"name": "Amphi Rataud",
"address": "45 rue d'Ulm, NIR, niveau PB"
}
]

View file

@ -1,100 +0,0 @@
[
{
"description": "Jazz / Funk",
"title": "Un super concert",
"price": 10.0,
"slots_description": "Debout",
"slots": 5
},
{
"description": "Homemade",
"title": "Une super pi\u00e8ce",
"price": 10.0,
"slots_description": "Assises",
"slots": 60
},
{
"description": "Plein air, soleil, bonne musique",
"title": "Concert pour la f\u00eate de la musique",
"price": 5.0,
"slots_description": "Debout, attention \u00e0 la fontaine",
"slots": 30
},
{
"description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur",
"title": "Op\u00e9ra sans d\u00e9cors",
"price": 5.0,
"slots_description": "Assis sur l'herbe",
"slots": 20
},
{
"description": "Buffet \u00e0 la fin",
"title": "Concert Trouv\u00e8re",
"price": 20.0,
"slots_description": "Assises",
"slots": 15
},
{
"description": "Vive les maths",
"title": "Dessin \u00e0 la craie sur tableau noir",
"price": 10.0,
"slots_description": "Assises, tablette pour prendre des notes",
"slots": 30
},
{
"description": "Une pi\u00e8ce \u00e0 un personnage",
"title": "D\u00e9cors, d\u00e9montage en musique",
"price": 0.0,
"slots_description": "Assises",
"slots": 20
},
{
"description": "Annulera, annulera pas\u00a0?",
"title": "La Nuit",
"price": 27.0,
"slots_description": "",
"slots": 1000
},
{
"description": "Le boum fait sa carte blanche",
"title": "Turbomix",
"price": 10.0,
"slots_description": "Debout les mains en l'air",
"slots": 20
},
{
"description": "Unique repr\u00e9sentation",
"title": "Carinettes et trombone",
"price": 15.0,
"slots_description": "Chaises ikea",
"slots": 10
},
{
"description": "Suivi d'une jam session",
"title": "Percussion sur rondins",
"price": 5.0,
"slots_description": "B\u00fbches",
"slots": 14
},
{
"description": "\u00c9preuve sportive et artistique",
"title": "Bassin aux ernests, nage libre",
"price": 5.0,
"slots_description": "Humides",
"slots": 10
},
{
"description": "Sonore",
"title": "Chant du barde",
"price": 13.0,
"slots_description": "Ne venez pas",
"slots": 20
},
{
"description": "Cocorico",
"title": "Chant du coq",
"price": 4.0,
"slots_description": "bancs",
"slots": 15
}
]

View file

@ -1,108 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Attribution',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('given', models.BooleanField(default=False, verbose_name='Donn\xe9e')),
],
),
migrations.CreateModel(
name='ChoixSpectacle',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('priority', models.PositiveIntegerField(verbose_name=b'Priorit\xc3\xa9')),
('double_choice', models.CharField(default=b'1', max_length=10, verbose_name=b'Nombre de places', choices=[(b'1', b'1 place'), (b'autoquit', b'2 places si possible, 1 sinon'), (b'double', b'2 places sinon rien')])),
],
options={
'ordering': ('priority',),
'verbose_name': 'voeu',
'verbose_name_plural': 'voeux',
},
),
migrations.CreateModel(
name='Participant',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('paid', models.BooleanField(default=False, verbose_name='A pay\xe9')),
('paymenttype', models.CharField(blank=True, max_length=6, verbose_name='Moyen de paiement', choices=[(b'cash', 'Cash'), (b'cb', b'CB'), (b'cheque', 'Ch\xe8que'), (b'autre', 'Autre')])),
],
),
migrations.CreateModel(
name='Salle',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=300, verbose_name=b'Nom')),
('address', models.TextField(verbose_name=b'Adresse')),
],
),
migrations.CreateModel(
name='Spectacle',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
('date', models.DateTimeField(verbose_name=b'Date & heure')),
('description', models.TextField(verbose_name=b'Description', blank=True)),
('slots_description', models.TextField(verbose_name=b'Description des places', blank=True)),
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
('slots', models.IntegerField(verbose_name=b'Places')),
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
('location', models.ForeignKey(to='bda.Salle')),
],
options={
'ordering': ('priority', 'date', 'title'),
'verbose_name': 'Spectacle',
},
),
migrations.AddField(
model_name='participant',
name='attributions',
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
),
migrations.AddField(
model_name='participant',
name='choices',
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
),
migrations.AddField(
model_name='participant',
name='user',
field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='choixspectacle',
name='participant',
field=models.ForeignKey(to='bda.Participant'),
),
migrations.AddField(
model_name='choixspectacle',
name='spectacle',
field=models.ForeignKey(related_name='participants', to='bda.Spectacle'),
),
migrations.AddField(
model_name='attribution',
name='participant',
field=models.ForeignKey(to='bda.Participant'),
),
migrations.AddField(
model_name='attribution',
name='spectacle',
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'),
),
migrations.AlterUniqueTogether(
name='choixspectacle',
unique_together=set([('participant', 'spectacle')]),
),
]

View file

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.utils import timezone
def forwards_func(apps, schema_editor):
Tirage = apps.get_model("bda", "Tirage")
db_alias = schema_editor.connection.alias
Tirage.objects.using(db_alias).bulk_create([
Tirage(
id=1,
title="Tirage de test (migration)",
active=False,
ouverture=timezone.now(),
fermeture=timezone.now()),
])
class Migration(migrations.Migration):
dependencies = [
('bda', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Tirage',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
('ouverture', models.DateTimeField(verbose_name=b"Date et heure d'ouverture du tirage")),
('fermeture', models.DateTimeField(verbose_name=b'Date et heure de fermerture du tirage')),
('token', models.TextField(verbose_name=b'Graine du tirage', blank=True)),
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
],
),
migrations.RunPython(forwards_func, migrations.RunPython.noop),
migrations.AlterField(
model_name='participant',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='participant',
name='tirage',
field=models.ForeignKey(default=1, to='bda.Tirage'),
preserve_default=False,
),
migrations.AddField(
model_name='spectacle',
name='tirage',
field=models.ForeignKey(default=1, to='bda.Tirage'),
preserve_default=False,
),
]

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bda', '0002_add_tirage'),
]
operations = [
migrations.AlterField(
model_name='spectacle',
name='price',
field=models.FloatField(verbose_name=b"Prix d'une place"),
),
migrations.AlterField(
model_name='tirage',
name='active',
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bda', '0003_update_tirage_and_spectacle'),
]
operations = [
migrations.AddField(
model_name='spectacle',
name='listing',
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
preserve_default=False,
),
migrations.AddField(
model_name='spectacle',
name='rappel_sent',
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
),
]

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bda', '0004_mails-rappel'),
]
operations = [
migrations.AlterField(
model_name='choixspectacle',
name='priority',
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
),
migrations.AlterField(
model_name='spectacle',
name='priority',
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
),
migrations.AlterField(
model_name='spectacle',
name='rappel_sent',
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
),
]

View file

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.utils import timezone
def forwards_func(apps, schema_editor):
Tirage = apps.get_model("bda", "Tirage")
db_alias = schema_editor.connection.alias
for tirage in Tirage.objects.using(db_alias).all():
if tirage.tokens:
tirage.tokens = "Before %s\n\"\"\"%s\"\"\"\n" % (
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
tirage.tokens)
tirage.save()
class Migration(migrations.Migration):
dependencies = [
('bda', '0005_encoding'),
]
operations = [
migrations.RenameField('tirage', 'token', 'tokens'),
migrations.AddField(
model_name='tirage',
name='enable_do_tirage',
field=models.BooleanField(
default=False,
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
),
migrations.RunPython(forwards_func, migrations.RunPython.noop),
]

View file

@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bda', '0006_add_tirage_switch'),
]
operations = [
migrations.CreateModel(
name='CategorieSpectacle',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('name', models.CharField(max_length=100, verbose_name='Nom',
unique=True)),
],
options={
'verbose_name': 'Cat\xe9gorie',
},
),
migrations.CreateModel(
name='Quote',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('text', models.TextField(verbose_name='Citation')),
('author', models.CharField(max_length=200,
verbose_name='Auteur')),
],
),
migrations.AlterModelOptions(
name='spectacle',
options={'ordering': ('date', 'title'),
'verbose_name': 'Spectacle'},
),
migrations.RemoveField(
model_name='spectacle',
name='priority',
),
migrations.AddField(
model_name='spectacle',
name='ext_link',
field=models.CharField(
max_length=500,
verbose_name='Lien vers le site du spectacle',
blank=True),
),
migrations.AddField(
model_name='spectacle',
name='image',
field=models.ImageField(upload_to='imgs/shows/', null=True,
verbose_name='Image', blank=True),
),
migrations.AlterField(
model_name='tirage',
name='enable_do_tirage',
field=models.BooleanField(
default=False,
verbose_name='Le tirage peut \xeatre lanc\xe9'),
),
migrations.AlterField(
model_name='tirage',
name='tokens',
field=models.TextField(verbose_name='Graine(s) du tirage',
blank=True),
),
migrations.AddField(
model_name='spectacle',
name='category',
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
null=True),
),
migrations.AddField(
model_name='spectacle',
name='vips',
field=models.TextField(verbose_name='Personnalit\xe9s',
blank=True),
),
migrations.AddField(
model_name='quote',
name='spectacle',
field=models.ForeignKey(to='bda.Spectacle'),
),
]

View file

@ -1,103 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bda', '0007_extends_spectacle'),
]
operations = [
migrations.AlterField(
model_name='choixspectacle',
name='double_choice',
field=models.CharField(
verbose_name='Nombre de places',
choices=[('1', '1 place'),
('autoquit', '2 places si possible, 1 sinon'),
('double', '2 places sinon rien')],
max_length=10, default='1'),
),
migrations.AlterField(
model_name='participant',
name='paymenttype',
field=models.CharField(
blank=True,
choices=[('cash', 'Cash'), ('cb', 'CB'),
('cheque', 'Chèque'), ('autre', 'Autre')],
max_length=6, verbose_name='Moyen de paiement'),
),
migrations.AlterField(
model_name='salle',
name='address',
field=models.TextField(verbose_name='Adresse'),
),
migrations.AlterField(
model_name='salle',
name='name',
field=models.CharField(verbose_name='Nom', max_length=300),
),
migrations.AlterField(
model_name='spectacle',
name='date',
field=models.DateTimeField(verbose_name='Date & heure'),
),
migrations.AlterField(
model_name='spectacle',
name='description',
field=models.TextField(verbose_name='Description', blank=True),
),
migrations.AlterField(
model_name='spectacle',
name='listing',
field=models.BooleanField(
verbose_name='Les places sont sur listing'),
),
migrations.AlterField(
model_name='spectacle',
name='price',
field=models.FloatField(verbose_name="Prix d'une place"),
),
migrations.AlterField(
model_name='spectacle',
name='slots',
field=models.IntegerField(verbose_name='Places'),
),
migrations.AlterField(
model_name='spectacle',
name='slots_description',
field=models.TextField(verbose_name='Description des places',
blank=True),
),
migrations.AlterField(
model_name='spectacle',
name='title',
field=models.CharField(verbose_name='Titre', max_length=300),
),
migrations.AlterField(
model_name='tirage',
name='active',
field=models.BooleanField(verbose_name='Tirage actif',
default=False),
),
migrations.AlterField(
model_name='tirage',
name='fermeture',
field=models.DateTimeField(
verbose_name='Date et heure de fermerture du tirage'),
),
migrations.AlterField(
model_name='tirage',
name='ouverture',
field=models.DateTimeField(
verbose_name="Date et heure d'ouverture du tirage"),
),
migrations.AlterField(
model_name='tirage',
name='title',
field=models.CharField(verbose_name='Titre', max_length=300),
),
]

View file

@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('bda', '0008_py3'),
]
operations = [
migrations.CreateModel(
name='SpectacleRevente',
fields=[
('id', models.AutoField(serialize=False, primary_key=True,
auto_created=True, verbose_name='ID')),
('date', models.DateTimeField(
verbose_name='Date de mise en vente',
default=django.utils.timezone.now)),
('notif_sent', models.BooleanField(
verbose_name='Notification envoyée', default=False)),
('tirage_done', models.BooleanField(
verbose_name='Tirage effectué', default=False)),
],
options={
'verbose_name': 'Revente',
},
),
migrations.AddField(
model_name='participant',
name='choicesrevente',
field=models.ManyToManyField(to='bda.Spectacle',
related_name='subscribed',
blank=True),
),
migrations.AddField(
model_name='spectaclerevente',
name='answered_mail',
field=models.ManyToManyField(to='bda.Participant',
related_name='wanted',
blank=True),
),
migrations.AddField(
model_name='spectaclerevente',
name='attribution',
field=models.OneToOneField(to='bda.Attribution',
related_name='revente'),
),
migrations.AddField(
model_name='spectaclerevente',
name='seller',
field=models.ForeignKey(to='bda.Participant',
verbose_name='Vendeur',
related_name='original_shows'),
),
migrations.AddField(
model_name='spectaclerevente',
name='soldTo',
field=models.ForeignKey(to='bda.Participant',
verbose_name='Vendue à', null=True,
blank=True),
),
]

View file

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.utils import timezone
from datetime import timedelta
def forwards_func(apps, schema_editor):
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
for revente in SpectacleRevente.objects.all():
is_expired = timezone.now() > revente.date_tirage()
is_direct = (revente.attribution.spectacle.date >= revente.date and
timezone.now() > revente.date + timedelta(minutes=15))
revente.shotgun = is_expired or is_direct
revente.save()
class Migration(migrations.Migration):
dependencies = [
('bda', '0009_revente'),
]
operations = [
migrations.AddField(
model_name='spectaclerevente',
name='shotgun',
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
),
migrations.RunPython(forwards_func, migrations.RunPython.noop),
]

View file

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bda', '0010_spectaclerevente_shotgun'),
]
operations = [
migrations.AddField(
model_name='tirage',
name='appear_catalogue',
field=models.BooleanField(
default=False,
verbose_name='Tirage à afficher dans le catalogue'
),
),
]

View file

@ -1,344 +0,0 @@
# -*- coding: utf-8 -*-
import calendar
import random
from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail
from django.contrib.sites.models import Site
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.utils import timezone, formats
class Tirage(models.Model):
title = models.CharField("Titre", max_length=300)
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
tokens = models.TextField("Graine(s) du tirage", blank=True)
active = models.BooleanField("Tirage actif", default=False)
appear_catalogue = models.BooleanField(
"Tirage à afficher dans le catalogue",
default=False
)
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
default=False)
def __str__(self):
return "%s - %s" % (self.title, formats.localize(
timezone.template_localtime(self.fermeture)))
class Salle(models.Model):
name = models.CharField("Nom", max_length=300)
address = models.TextField("Adresse")
def __str__(self):
return self.name
class CategorieSpectacle(models.Model):
name = models.CharField('Nom', max_length=100, unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Catégorie"
class Spectacle(models.Model):
title = models.CharField("Titre", max_length=300)
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
date = models.DateTimeField("Date & heure")
location = models.ForeignKey(Salle)
vips = models.TextField('Personnalités', blank=True)
description = models.TextField("Description", blank=True)
slots_description = models.TextField("Description des places", blank=True)
image = models.ImageField('Image', blank=True, null=True,
upload_to='imgs/shows/')
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
max_length=500)
price = models.FloatField("Prix d'une place")
slots = models.IntegerField("Places")
tirage = models.ForeignKey(Tirage)
listing = models.BooleanField("Les places sont sur listing")
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
null=True)
class Meta:
verbose_name = "Spectacle"
ordering = ("date", "title",)
def timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple())
def __str__(self):
return "%s - %s, %s, %.02f" % (
self.title,
formats.localize(timezone.template_localtime(self.date)),
self.location,
self.price
)
def getImgUrl(self):
"""
Cette fonction permet d'obtenir l'URL de l'image, si elle existe
"""
try:
return self.image.url
except:
return None
def send_rappel(self):
"""
Envoie un mail de rappel à toutes les personnes qui ont une place pour
ce spectacle.
"""
# On récupère la liste des participants
members = {}
for attr in Attribution.objects.filter(spectacle=self).all():
member = attr.participant.user
if member.id in members:
members[member.id][1] = 2
else:
members[member.id] = [member, 1]
# FIXME : faire quelque chose de ça, un utilisateur bda_generic ?
# # Pour le BdA
# members[0] = ['BdA', 1, 'bda@ens.fr']
# members[-1] = ['BdA', 2, 'bda@ens.fr']
# On écrit un mail personnalisé à chaque participant
datatuple = [(
'bda-rappel',
{'member': member[0], 'nb_attr': member[1], 'show': self},
settings.MAIL_DATA['rappels']['FROM'],
[member[0].email])
for member in members.values()
]
send_mass_custom_mail(datatuple)
# On enregistre le fait que l'envoi a bien eu lieu
self.rappel_sent = timezone.now()
self.save()
# On renvoie la liste des destinataires
return members.values()
@property
def is_past(self):
return self.date < timezone.now()
class Quote(models.Model):
spectacle = models.ForeignKey(Spectacle)
text = models.TextField('Citation')
author = models.CharField('Auteur', max_length=200)
PAYMENT_TYPES = (
("cash", "Cash"),
("cb", "CB"),
("cheque", "Chèque"),
("autre", "Autre"),
)
class Participant(models.Model):
user = models.ForeignKey(User)
choices = models.ManyToManyField(Spectacle,
through="ChoixSpectacle",
related_name="chosen_by")
attributions = models.ManyToManyField(Spectacle,
through="Attribution",
related_name="attributed_to")
paid = models.BooleanField("A payé", default=False)
paymenttype = models.CharField("Moyen de paiement",
max_length=6, choices=PAYMENT_TYPES,
blank=True)
tirage = models.ForeignKey(Tirage)
choicesrevente = models.ManyToManyField(Spectacle,
related_name="subscribed",
blank=True)
def __str__(self):
return "%s - %s" % (self.user, self.tirage.title)
DOUBLE_CHOICES = (
("1", "1 place"),
("autoquit", "2 places si possible, 1 sinon"),
("double", "2 places sinon rien"),
)
class ChoixSpectacle(models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name="participants")
priority = models.PositiveIntegerField("Priorité")
double_choice = models.CharField("Nombre de places",
default="1", choices=DOUBLE_CHOICES,
max_length=10)
def get_double(self):
return self.double_choice != "1"
double = property(get_double)
def get_autoquit(self):
return self.double_choice == "autoquit"
autoquit = property(get_autoquit)
def __str__(self):
return "Vœux de %s pour %s" % (
self.participant.user.get_full_name(),
self.spectacle.title)
class Meta:
ordering = ("priority",)
unique_together = (("participant", "spectacle",),)
verbose_name = "voeu"
verbose_name_plural = "voeux"
class Attribution(models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
given = models.BooleanField("Donnée", default=False)
def __str__(self):
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
self.spectacle.date)
class SpectacleRevente(models.Model):
attribution = models.OneToOneField(Attribution,
related_name="revente")
date = models.DateTimeField("Date de mise en vente",
default=timezone.now)
answered_mail = models.ManyToManyField(Participant,
related_name="wanted",
blank=True)
seller = models.ForeignKey(Participant,
related_name="original_shows",
verbose_name="Vendeur")
soldTo = models.ForeignKey(Participant, blank=True, null=True,
verbose_name="Vendue à")
notif_sent = models.BooleanField("Notification envoyée",
default=False)
tirage_done = models.BooleanField("Tirage effectué",
default=False)
shotgun = models.BooleanField("Disponible immédiatement",
default=False)
@property
def date_tirage(self):
"""Renvoie la date du tirage au sort de la revente."""
# L'acheteur doit être connu au plus 12h avant le spectacle
remaining_time = (self.attribution.spectacle.date
- self.date - timedelta(hours=13))
# Au minimum, on attend 2 jours avant le tirage
delay = min(remaining_time, timedelta(days=2))
# Le vendeur a aussi 1h pour changer d'avis
return self.date + delay + timedelta(hours=1)
def __str__(self):
return "%s -- %s" % (self.seller,
self.attribution.spectacle.title)
class Meta:
verbose_name = "Revente"
def send_notif(self):
"""
Envoie une notification pour indiquer la mise en vente d'une place sur
BdA-Revente à tous les intéressés.
"""
inscrits = self.attribution.spectacle.subscribed.select_related('user')
datatuple = [(
'bda-revente',
{
'member': participant.user,
'show': self.attribution.spectacle,
'revente': self,
'site': Site.objects.get_current()
},
settings.MAIL_DATA['revente']['FROM'],
[participant.user.email])
for participant in inscrits
]
send_mass_custom_mail(datatuple)
self.notif_sent = True
self.save()
def mail_shotgun(self):
"""
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
leur indiquer qu'il est désormais disponible au shotgun.
"""
inscrits = self.attribution.spectacle.subscribed.select_related('user')
datatuple = [(
'bda-shotgun',
{
'member': participant.user,
'show': self.attribution.spectacle,
'site': Site.objects.get_current(),
},
settings.MAIL_DATA['revente']['FROM'],
[participant.user.email])
for participant in inscrits
]
send_mass_custom_mail(datatuple)
self.notif_sent = True
# Flag inutile, sauf si l'horloge interne merde
self.tirage_done = True
self.shotgun = True
self.save()
def tirage(self):
"""
Lance le tirage au sort associé à la revente. Un gagnant est choisi
parmis les personnes intéressées par le spectacle. Les personnes sont
ensuites prévenues par mail du résultat du tirage.
"""
inscrits = list(self.answered_mail.all())
spectacle = self.attribution.spectacle
seller = self.seller
if inscrits:
# Envoie un mail au gagnant et au vendeur
winner = random.choice(inscrits)
self.soldTo = winner
datatuple = []
context = {
'acheteur': winner.user,
'vendeur': seller.user,
'show': spectacle,
}
datatuple.append((
'bda-revente-winner',
context,
settings.MAIL_DATA['revente']['FROM'],
[winner.user.email],
))
datatuple.append((
'bda-revente-seller',
context,
settings.MAIL_DATA['revente']['FROM'],
[seller.user.email]
))
# Envoie un mail aux perdants
for inscrit in inscrits:
if inscrit != winner:
new_context = dict(context)
new_context['acheteur'] = inscrit.user
datatuple.append((
'bda-revente-loser',
new_context,
settings.MAIL_DATA['revente']['FROM'],
[inscrit.user.email]
))
send_mass_custom_mail(datatuple)
# Si personne ne veut de la place, elle part au shotgun
else:
self.shotgun = True
self.tirage_done = True
self.save()

View file

@ -1,48 +0,0 @@
form#tokenform {
text-align: center;
font-size: 2em;
}
label {
margin-right: 10px;
vertical-align: top;
}
form#tokenform textarea {
font-size: 2em;
width: 350px;
height: 200px;
font-family: 'Droif Serif', serif;
}
/* wft ?
input {
width: 400px;
font-size: 2em;
}*/
ul.losers {
display: inline;
margin: 0;
padding: 0;
}
ul.losers li {
display: inline;
}
span.details {
font-size: 0.7em;
}
td {
border: 0px solid black;
padding: 2px;
}
.attribresult {
margin: 10px 0px;
}
.spectacle-passe {
opacity:0.5;
}

Binary file not shown.

View file

@ -1,28 +0,0 @@
{% extends "bda-attrib.html" %}
{% block extracontent %}
<h2>Attributions (détails)</h2>
<h3 class="horizontal-title">Token :</h3>
<pre>{{ token }}</pre>
<h3 class="horizontal-title">Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</h3>
<table>
{% for member, shows in members2 %}
<tr>
<td>{{ member.user.get_full_name }}</td>
<td>{{ member.user.email }}</td>
<td>Total: {{ member.total }}€</td>
<td style="width: 120px;"></td>
</tr>
{% for show in shows %}
<tr>
<td></td>
<td></td>
<td>{{ show }}</td>
<td></td>
</tr>
{% endfor %}
{% endfor %}
</table>
{% endblock %}

View file

@ -1,49 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block extra_head %}
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
{% endblock %}
{% block realcontent %}
<h2>Attributions</h2>
<br />
<p class="success">Pour raison de sécurité, le lancement du tirage
a été désactivé. Vous pouvez le réactiver dans
l'<a href="{% url "admin:index" %}">interface admin</a></p>
<h3 class="horizontal-title">Token :</h3>
<pre>{{ token }}</pre>
<h3 class="horizontal-title">Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</h3>
{% if user.profile.is_buro %}<h3 class="horizontal-title">Déficit total: {{ total_deficit }} €, Opéra: {{ opera_deficit }} €, Attribué: {{ total_sold }} €</h3>{% endif %}
<h3 class="horizontal-title">Temps de calcul : {{ duration|floatformat }}s</h3>
{% for show, members, losers in results %}
<div class="attribresult">
<h3 class="horizontal-title">{{ show.title }} - {{ show.date }} @ {{ show.location }}</h3>
<p>
<strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong>
{{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }} de déficit{% endif %}
</p>
Places :
<ul>
{% for member, rank, origrank, double in members %}
<li>{{ member.user.get_full_name }} <span class="details">(souhait {{ origrank }} &mdash; rang {{ rank }})</span></li>
{% endfor %}
</ul>
Déçus :
{% if not losers %}/{% else %}
<ul class="losers">
{% for member, rank, origrank, double in losers %}
{% if not forloop.first %} ; {% endif %}
<li>{{ member.user.get_full_name }} <span class="details">(souhait {{ origrank }} &mdash; rang {{ rank }})</span></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
{% block extracontent %}
{% endblock %}
{% endblock %}

View file

@ -1,7 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>{{ spectacle }}</h2>
<textarea style="width: 100%; height: 100px; margin-top: 10px;">
{% for attrib in spectacle.attribues.all %}{{ attrib.participant.user.email }}, {% endfor %}</textarea>
{% endblock %}

View file

@ -1,9 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>Inscription à une revente</h2>
<p class="success"> Votre inscription a bien été enregistrée !</p>
<p>Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}.
{% endblock %}

View file

@ -1,6 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>BdA-Revente</h2>
<p>Il n'y a plus de places en revente pour ce spectacle, désolé !</p>
{% endblock %}

View file

@ -1,6 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2><strong>Nope</strong></h2>
<p>Avant de revendre des places, il faut aller les payer !</p>
{% endblock %}

View file

@ -1,64 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>{{ spectacle }}</h2>
<table class='table table-striped etat-bda'>
<thead>
<tr>
<th data-sort="string">Nom</th>
<th data-sort="int">Places</th>
<th data-sort="string">Adresse Mail</th>
<th data-sort="string">Payé</th>
<th data-sort="string">Donné</th>
</tr>
</thead>
<tbody>
{% for participant in participants %}
<tr>
<td data-sort-value="{{ participan.name}}">{{participant.name}}</td>
<td data-sort-value="{{participant.nb_places}}">{{participant.nb_places}} place{{participant.nb_places|pluralize}}</td>
<td data-sort-value="{{participant.email}}">{{participant.email}}</td>
<td data-sort-value="{{ participant.paid}}" class={%if participant.paid %}"greenratio"{%else%}"redratio"{%endif%}>
{% if participant.paid %}Oui{% else %}Non{%endif%}
</td>
<td data-sort-value="{{participant.given}}" class={%if participant.given == participant.nb_places %}"greenratio"
{%elif participant.given == 0%}"redratio"
{%else%}"orangeratio"
{%endif%}>
{% if participant.given == participant.nb_places %}Oui
{% elif participant.given == 0 %}Non
{% else %}{{participant.given}}/{{participant.nb_places}}
{%endif%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3><a href="{% url "admin:bda_attribution_add" %}?spectacle={{spectacle.id}}"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une attribution</a></h3>
<br>
<button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button>
<pre id="export-mails" style="display:none">
{%for participant in participants %}{{participant.email}}, {%endfor%}
</pre>
<br>
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
<pre id="export-salle" style="display:none">
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
{% endfor %}
</pre>
<script type="text/javascript"
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
<script>
function toggle(id) {
var pre = document.getElementById(id) ;
pre.style.display = pre.style.display == "none" ? "block" : "none" ;
}
</script>
<script type="text/javascript">
$(function(){
$("table.etat-bda").stupidtable();
});
</script>
{% endblock %}

View file

@ -1,14 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Places disponibles immédiatement</h2>
{% if shotgun %}
<ul class="list-unstyled">
{% for spectacle in shotgun %}
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
{% endfor %}
{% else %}
<p> Pas de places disponibles immédiatement, désolé !</p>
{% endif %}
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>Revente de place</h2>
<p class="success">Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !</p>
{% endblock %}

View file

@ -1,13 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Tirage au sort du BdA</h2>
<form action="" method="post" id="tokenform">
{% csrf_token %}
<strong>La graine :</strong>
<div>
{{ form.token }}
</div>
<input type="submit" onsubmit="return confirm('Voulez vous lancer le Tirage maintenant ?\n\nCECI REMETTRA À ZÉRO TOUTES LES DONNÉES si le tirage a déjà été lancé.')" value="Go" />
</form>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Impayés</h2>
<textarea style="width: 100%; height: 100px; margin-top: 10px;">
{% for participant in unpaid %}{{ participant.user.email }}, {% endfor %}</textarea>
<h3>Total&nbsp: {{ unpaid|length }}</h3>
{% endblock %}

View file

@ -1,13 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Nope</h2>
{% if revente.shotgun %}
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
<p>Si personne n'était intéressé, elle est maintenant disponible
<a href="{% url "bda-buy-revente" revente.attribution.spectacle.id %}">ici</a>.</p>
{% else %}
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
{% endif %}
{% endblock %}

View file

@ -1,53 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>État des inscriptions BdA</h2>
<table class="table table-striped etat-bda">
<thead>
<tr>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Places</th>
<th data-sort="int">Demandes</th>
<th data-sort="float">Ratio</th>
</tr>
</thead>
<tbody>
{% for spectacle in spectacles %}
<tr>
<td>{{ spectacle.title }}</td>
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td>
<td data-sort-value="{{ spectacle.ratio |stringformat:".3f" }}"
class={% if spectacle.ratio < 1.0 %}
"greenratio"
{% else %}
{% if spectacle.ratio < 2.5 %}
"orangeratio"
{% else %}
"redratio"
{% endif %}
{% endif %}>
{{ spectacle.ratio |floatformat }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<span class="bda-prix">
Total&nbsp;: {{ total }} place{{ total|pluralize }} demandée{{ total|pluralize }}
sur {{ proposed }} place{{ proposed|pluralize }} proposée{{ proposed|pluralize }}
</span>
<script type="text/javascript"
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
</script>
<script type="text/javascript">
$(function(){
$("table.etat-bda").stupidtable();
});
</script>
{% endblock %}

View file

@ -1,41 +0,0 @@
{% load bootstrap %}
{{ formset.non_form_errors.as_ul }}
<table id="bda_formset" class="form table">
{{ formset.management_form }}
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
{% if field.name != "DELETE" and field.name != "priority" %}
<th class="bda-field-{{ field.name }}">{{ field.label|safe|capfirst }}</th>
{% endif %}
{% endfor %}
<th><sup>1</sup></th>
</tr></thead>
<tbody class="bda_formset_content">
{% endif %}
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
{% for field in form.visible_fields %}
{% if field.name != "DELETE" and field.name != "priority" %}
<td class="bda-field-{{ field.name }}">
{% if forloop.first %}
{{ form.non_field_errors }}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field | bootstrap }}
</td>
{% endif %}
{% endfor %}
<td class="tools-cell"><div class="tools">
<a href="javascript://" class="glyphicon glyphicon-sort drag-btn" title="Déplacer"></a>
<input type="checkbox" name="{{ form.DELETE.html_name }}" style="display: none;" />
<input type="hidden" name="{{ form.priority.html_name }}" style="{{ form.priority.value }}" />
<a href="javascript://" class="glyphicon glyphicon-remove remove-btn" title="Supprimer"></a>
</div>
<div class="spacer"></div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -1,115 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block extra_head %}
<script src="{% static 'js/jquery-ui.min.js' %}" type="text/javascript"></script>
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
{% endblock %}
{% block realcontent %}
<script type="text/javascript">
var django = {
"jQuery": jQuery.noConflict(true)
};
(function($) {
cloneMore = function(selector, type) {
var newElement = $(selector).clone(true);
var total = $('#id_' + type + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
});
newElement.find('label').each(function() {
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
$(this).attr('for', newFor);
});
total++;
$('#id_' + type + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
}
deleteButtonHandler = function(elem) {
elem.bind("click", function() {
var deleteInput = $(this).prev().prev(),
form = $(this).parents(".dynamic-form").first();
// callback
// toggle options.predeleteCssClass and toggle checkbox
if (form.hasClass("has_original")) {
form.toggleClass("predelete");
if (deleteInput.attr("checked")) {
deleteInput.attr("checked", false);
} else {
deleteInput.attr("checked", true);
}
}
// callback
});
};
$(document).ready(function($) {
deleteButtonHandler($("table#bda_formset tbody.bda_formset_content").find("a.remove-btn"));
$("table#bda_formset tbody.bda_formset_content").sortable({
handle: "a.drag-btn",
items: "tr",
axis: "y",
appendTo: 'body',
forceHelperSize: true,
placeholder: 'ui-sortable-placeholder',
forcePlaceholderSize: true,
containment: 'form#bda_form',
tolerance: 'pointer',
start: function(evt, ui) {
var template = "",
len = ui.item.children("td").length;
for (var i = 0; i < len; i++) {
template += "<td style='height:" + (ui.item.outerHeight() + 12 ) + "px' class='placeholder-cell'>&nbsp;</td>"
}
template += "";
ui.placeholder.html(template);
},
stop: function(evt, ui) {
// Toggle div.table twice to remove webkits border-spacing bug
$("table#bda_formset").toggle().toggle();
},
});
$("#bda_form").bind("submit", function(){
var sortable_field_name = "priority";
var i = 1;
$(".bda_formset_content").find("tr").each(function(){
var fields = $(this).find("td :input[value]"),
select = $(this).find("td select");
if (select.val() && fields.serialize()) {
$(this).find("input[name$='"+sortable_field_name+"']").val(i);
i++;
}
});
});
});
})(django.jQuery);
</script>
<h2 class="no-bottom-margin">Inscription au tirage au sort du BdA</h2>
<form class="form-horizontal" id="bda_form" method="post" action="{% url 'bda-tirage-inscription' tirage.id %}">
{% csrf_token %}
{% include "bda/inscription-formset.html" %}
<div class="inscription-bottom">
<span class="bda-prix">Prix total actuel : {{ total_price }}€</span>
<div class="pull-right">
<input type="button" class="btn btn-default" value="Ajouter un autre v&oelig;u" id="add_more">
<script>
django.jQuery('#add_more').click(function() {
cloneMore('tbody.bda_formset_content tr:last-child', 'choixspectacle_set');
});
</script>
<input type="hidden" name="dbstate" value="{{ dbstate }}" />
<input type="submit" class="btn btn-primary" value="Enregistrer" />
</div>
<p class="footnotes">
<sup>1</sup>: cette liste de v&oelig;ux est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque v&oelig;u.<br />
</p>
</div>
</form>
{% endblock %}

View file

@ -1,33 +0,0 @@
{% extends "base_title.html" %}
{% load bootstrap %}
{% block realcontent %}
<h2>Inscriptions pour BdA-Revente</h2>
<form action="" class="form-horizontal" method="post">
{% csrf_token %}
<div class="form-group">
<h3>Spectacles</h3>
<br/>
<button type="button" class="btn btn-primary" onClick="select(true)">Tout sélectionner</button>
<button type="button" class="btn btn-primary" onClick="select(false)">Tout désélectionner</button>
<div class="multiple-checkbox">
<ul>
{% for checkbox in form.spectacles %}
<li>{{checkbox}}</li>
{%endfor%}
</ul>
</div>
</div>
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
</form>
<script language="JavaScript">
function select(check) {
checkboxes = document.getElementsByName("spectacles");
for(var i=0, n=checkboxes.length;i<n;i++) {
checkboxes[i].checked = check;
}
}
</script>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Mails de rappels</h2>
{% if sent %}
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
<ul>
{% for member in members %}
<li>{{ member.get_full_name }} ({{ member.email }})</li>
{% endfor %}
</ul>
{% else %}
<h3>Voulez vous envoyer les mails de rappel pour le spectacle
{{ show.title }}&nbsp;?</h3>
{% if show.rappel_sent %}
<p class="error">Attention, les mails ont déjà été envoyés le
{{ show.rappel_sent }}</p>
{% endif %}
{% endif %}
{% if not sent %}
<form action="" method="post">
{% csrf_token %}
<div class="pull-right">
<input class="btn btn-primary" type="submit" value="Envoyer" />
</div>
</form>
{% endif %}
<br/>
<hr/>
<h3>Forme des mails</h3>
<h4>Une seule place</h4>
{% for part in exemple_mail_1place %}
<pre>{{ part }}</pre>
{% endfor %}
<h4>Deux places</h4>
{% for part in exemple_mail_2places %}
<pre>{{ part }}</pre>
{% endfor %}
{% endblock %}

View file

@ -1,14 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
{% if choices %}
<h3>Vos v&oelig;ux:</h3>
<ol>
{% for choice in choices %}
<li>{{ choice.spectacle }}{% if choice.double %} (deux places{% if autoquit %}, abandon automatique{% endif %}){% endif %}</li>
{% endfor %}
</ol>
{% else %}
<h3>Vous n'avez enregistré aucun v&oelig;u pour le tirage au sort</h3>
{% endif %}
{% endblock %}

View file

@ -1,24 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2><strong>Places attribuées</strong></h3>
{% if places %}
<table class="table table-striped">
{% for place in places %}
<tr>
<td>{{place.spectacle.title}}</td>
<td>{{place.spectacle.location}}</td>
<td>{{place.spectacle.date}}</td>
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
</tr>
{% endfor %}
</table>
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
<br/>
<p>Ne manque pas un spectacle avec le
<a href="{% url "gestioncof.views.calendar" %}">calendrier
automatique&#8239;!</a></p>
{% else %}
<h3>Vous n'avez aucune place :(</h3>
{% endif %}
{% endblock %}

View file

@ -1,53 +0,0 @@
{% extends "base_title.html" %}
{% load bootstrap %}
{% block realcontent %}
<h2>Revente de place</h2>
{% if resellform.attributions %}
<h3>Places non revendues</h3>
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{{resellform|bootstrap}}
<div class="form-actions">
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
</div>
</form>
{% endif %}
<br>
{% if annulform.attributions or overdue %}
<h3>Places en cours de revente</h3>
<form action="" method="post">
{% csrf_token %}
<div class='form-group'>
<div class='multiple-checkbox'>
<ul>
{% for attrib in annulform.attributions %}
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
{% endfor %}
{% for attrib in overdue %}
<li>
<input type="checkbox" style="visibility:hidden">
{{attrib.spectacle}}
</li>
{% endfor %}
{% if annulform.attributions %}
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
{% endif %}
</form>
{% endif %}
<br>
{% if soldform.attributions %}
<h3>Places revendues</h3>
<form action="" method="post">
{% csrf_token %}
{{soldform|bootstrap}}
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
</form>
{% endif %}
{% if not resellform.attributions and not soldform.attributions and not overdue and not annulform.attributions %}
<p>Plus de reventes possibles !</p>
{% endif %}
{% endblock %}

View file

@ -1,113 +0,0 @@
{% load staticfiles %}
<!doctype html>
<html>
<head>
<base target="_parent"/>
<style>
@font-face {
font-family: josefinsans;
src: url({% static "fonts/josefinsans.ttf" %});
}
*::-moz-selection {
background: #B0B0B0;
}
*::selection {
background: #B0B0B0;
}
.descTable{
width: 100%;
margin: 0 auto 1em;
border-bottom: 2px solid;
border-collapse: collapse;
border-spacing: 0;
font-size: 14px;
line-height: 2;
max-width: 100%;
background-color: transparent;
font-family: 'josefinsans', 'Arial';
font-weight: 700;
color: #5a5a5a;
}
img{
max-width: 100%;
}
</style>
<meta charset="utf8" />
</head>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<body>
{% for show in shows %}
<table class="descTable">
<thead>
<tr>
<th colspan="2"><p style="text-align:center;font-size:22px;">{{ show.title }}</p></th>
</tr>
</thead>
<tbody>
<tr>
<td><p style="text-align: left;">{{ show.location }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.category }}</p></td>
</tr>
<tr>
<td><p style="text-align: left;">{{ show.date|date:"l j F Y - H\hi" }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %} - {{ show.price }} euro{{ show.price|pluralize}}</p></td>
</tr>
{% if show.vips %}
<tr>
<td colspan="2"><p style="text-align: justify;">{{ show.vips }}</p></td>
</tr>
{% endif %}
<tr>
<td colspan="2">
<p style="text-align: justify;">{{ show.description }}</p>
{% for quote in show.quote_set.all %}
<p style="text-align:center; font-style: italic;">«{{ quote.text }}»{% if quote.author %} - {{ quote.author }}{% endif %}</p>
{% endfor %}
</td>
</tr>
{% if show.image %}
<tr>
<td colspan="2"><p style="text-align:center;"><a href="{{ show.ext_link }}"><img class="imgDesc" style="display: inline;" src="{{ MEDIA_URL }}{{ show.image }}" alt="{{ show.title }}"></a></p></td>
</tr>
{% endif %}
</tbody>
</table>
{% endfor %}
<script>
// Correction de la taille des images
/*$(document).ready(function() {
$(".descTable").each(function() {
$(this).width($("body").width());
});
$(".imgDesc").on("load", function() {
// Dimensions
origHeight = 500; // Hauteur souhaitée
w = $(this).width();
h = $(this).height();
r = w/h; // Ratio de l'image
maxWidth = $("body").width();
if (r * origHeight > maxWidth)
{
$(this).width(maxWidth);
$(this).height(maxWidth/r);
}
else
{
$(this).width(r * origHeight);
$(this).height(origHeight);
}
});
});*/
</script>
</body>
</html>

View file

@ -1,20 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{%block realcontent %}
<h2>Rachat d'une place</h2>
<form action="" method="post">
{% csrf_token %}
<pre>
Bonjour !
Je souhaiterais racheter ta place pour {{spectacle.title}} le {{spectacle.date}} ({{spectacle.location}}) à {{spectacle.price}}€.
Contacte-moi si tu es toujours intéressé-e !
{{user.get_full_name}} ({{user.email}})
</pre>
<input type="submit" class="btn btn-primary pull-right" value="Envoyer">
</form>
<p class="bda-prix">Note : ce mail sera envoyé à une personne au hasard revendant sa place.</p>
{%endblock%}

View file

@ -1,56 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block extra_head %}
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
{% endblock %}
{% block realcontent %}
<h2><strong>{{tirage_name}}</strong></h2>
<h3>Liste des spectacles</h3>
<table class="table table-striped table-hover etat-bda">
<thead>
<tr>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="float">Prix</th>
</tr>
</thead>
<tbody>
{% for spectacle in object_list %}
<tr class="clickable-row {% if spectacle.is_past %}spectacle-passe{% endif %}" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
<td data-sort-value="{{ spectacle.timestamp }}"">{{ spectacle.date }}</td>
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
{{ spectacle.price |floatformat }}€
</td>
</tr>
{% endfor %}
</tbody>
</table>
<script type="text/javascript"
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
</script>
<script type="text/javascript">
$(function(){
$("table.etat-bda").stupidtable();
});
</script>
<script>
jQuery(document).ready(function($) {
$(".clickable-row").click(function() {
window.document.location = $(this).data("href");
});
});
</script>
<h3> Exports </h3>
<ul>
<li><a href="{% url 'bda-unpaid' tirage_id %}">Mailing list impayés</a>
<li><a href="{% url 'bda-descriptions' tirage_id %}">Lien vers les descriptions des spectacles, à utiliser dans une page wordpress</a>
</ul>
{% endblock %}

View file

@ -1,10 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Raté, le tirage ne peut pas être lancé&#8239;!</h2>
<p>Soit les inscriptions ne sont en pas encore fermées, soit le lancement du
tirage est désactivé. Si vous savez ce que vous faites, vous pouvez autoriser
le lancement du tirage dans
l'<a href="{% url "admin:index" %}">interface admin</a>.</p>
{% endblock %}

View file

@ -1,79 +0,0 @@
import json
from django.test import TestCase, Client
from django.utils import timezone
from .models import Tirage, Spectacle, Salle, CategorieSpectacle
class TestBdAViews(TestCase):
def setUp(self):
self.tirage = Tirage.objects.create(
title="Test tirage",
appear_catalogue=True,
ouverture=timezone.now(),
fermeture=timezone.now(),
)
self.category = CategorieSpectacle.objects.create(name="Category")
self.location = Salle.objects.create(name="here")
Spectacle.objects.bulk_create([
Spectacle(
title="foo", date=timezone.now(), location=self.location,
price=0, slots=42, tirage=self.tirage, listing=False,
category=self.category
),
Spectacle(
title="bar", date=timezone.now(), location=self.location,
price=1, slots=142, tirage=self.tirage, listing=False,
category=self.category
),
Spectacle(
title="baz", date=timezone.now(), location=self.location,
price=2, slots=242, tirage=self.tirage, listing=False,
category=self.category
),
])
def test_catalogue(self):
"""Test the catalogue JSON API"""
client = Client()
# The `list` hooh
resp = client.get("/bda/catalogue/list")
self.assertJSONEqual(
resp.content.decode("utf-8"),
[{"id": self.tirage.id, "title": self.tirage.title}]
)
# The `details` hook
resp = client.get(
"/bda/catalogue/details?id={}".format(self.tirage.id)
)
self.assertJSONEqual(
resp.content.decode("utf-8"),
{
"categories": [{
"id": self.category.id,
"name": self.category.name
}],
"locations": [{
"id": self.location.id,
"name": self.location.name
}],
}
)
# The `descriptions` hook
resp = client.get(
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
)
raw = resp.content.decode("utf-8")
try:
results = json.loads(raw)
except ValueError:
self.fail("Not valid JSON: {}".format(raw))
self.assertEqual(len(results), 3)
self.assertEqual(
{(s["title"], s["price"], s["slots"]) for s in results},
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
)

View file

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django.conf.urls import url
from gestioncof.decorators import buro_required
from bda.views import SpectacleListView
from bda import views
urlpatterns = [
url(r'^inscription/(?P<tirage_id>\d+)$',
views.inscription,
name='bda-tirage-inscription'),
url(r'^places/(?P<tirage_id>\d+)$',
views.places,
name="bda-places-attribuees"),
url(r'^revente/(?P<tirage_id>\d+)$',
views.revente,
name='bda-revente'),
url(r'^etat-places/(?P<tirage_id>\d+)$',
views.etat_places,
name='bda-etat-places'),
url(r'^tirage/(?P<tirage_id>\d+)$', views.tirage),
url(r'^spectacles/(?P<tirage_id>\d+)$',
buro_required(SpectacleListView.as_view()),
name="bda-liste-spectacles"),
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
views.spectacle,
name="bda-spectacle"),
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
views.unpaid,
name="bda-unpaid"),
url(r'^liste-revente/(?P<tirage_id>\d+)$',
views.list_revente,
name="bda-liste-revente"),
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
views.buy_revente,
name="bda-buy-revente"),
url(r'^revente-interested/(?P<revente_id>\d+)$',
views.revente_interested,
name='bda-revente-interested'),
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
views.revente_shotgun,
name="bda-shotgun"),
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
name='bda-descriptions'),
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
name='bda-catalogue'),
]

View file

@ -1,749 +0,0 @@
# -*- coding: utf-8 -*-
import random
import hashlib
import time
import json
from datetime import timedelta
from custommail.shortcuts import (
send_mass_custom_mail, send_custom_mail, render_custom_mail
)
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db import models, transaction
from django.core import serializers
from django.db.models import Count, Q, Sum
from django.forms.models import inlineformset_factory
from django.http import (
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
)
from django.core.urlresolvers import reverse
from django.conf import settings
from django.utils import timezone, formats
from django.views.generic.list import ListView
from gestioncof.decorators import cof_required, buro_required
from bda.models import (
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
SpectacleRevente, Salle, Quote, CategorieSpectacle
)
from bda.algorithm import Algorithm
from bda.forms import (
BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm,
SoldForm
)
@cof_required
def etat_places(request, tirage_id):
"""
Résumé des spectacles d'un tirage avec pour chaque spectacle :
- Le nombre de places en jeu
- Le nombre de demandes
- Le ratio demandes/places
Et le total de toutes les demandes
"""
tirage = get_object_or_404(Tirage, id=tirage_id)
spectacles1 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \
.filter(double_choice="1") \
.all() \
.values('spectacle', 'spectacle__title') \
.annotate(total=models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \
.exclude(double_choice="1") \
.all() \
.values('spectacle', 'spectacle__title') \
.annotate(total=models.Count('spectacle'))
spectacles = tirage.spectacle_set.all()
spectacles_dict = {}
total = 0
for spectacle in spectacles:
spectacle.total = 0
spectacle.ratio = 0.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = \
spectacles_dict[spectacle["spectacle"]].total / \
spectacles_dict[spectacle["spectacle"]].slots
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = \
spectacles_dict[spectacle["spectacle"]].total / \
spectacles_dict[spectacle["spectacle"]].slots
total += 2*spectacle["total"]
context = {
"proposed": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'],
"spectacles": spectacles,
"total": total,
'tirage': tirage
}
return render(request, "bda/etat-places.html", context)
def _hash_queryset(queryset):
data = serializers.serialize("json", queryset).encode('utf-8')
hasher = hashlib.sha256()
hasher.update(data)
return hasher.hexdigest()
@cof_required
def places(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
places = participant.attribution_set.order_by(
"spectacle__date", "spectacle").all()
total = sum([place.spectacle.price for place in places])
filtered_places = []
places_dict = {}
spectacles = []
dates = []
warning = False
for place in places:
if place.spectacle in spectacles:
places_dict[place.spectacle].double = True
else:
place.double = False
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
date = place.spectacle.date.date()
if date in dates:
warning = True
else:
dates.append(date)
# On prévient l'utilisateur s'il a deux places à la même date
if warning:
messages.warning(request, "Attention, vous avez reçu des places pour "
"des spectacles différents à la même date.")
return render(request, "bda/resume_places.html",
{"participant": participant,
"places": filtered_places,
"tirage": tirage,
"total": total})
@cof_required
def inscription(request, tirage_id):
"""
Vue d'inscription à un tirage BdA.
- On vérifie qu'on se situe bien entre la date d'ouverture et la date de
fermeture des inscriptions.
- On vérifie que l'inscription n'a pas été modifiée entre le moment le
client demande le formulaire et le moment il soumet son inscription
(autre session par exemple).
"""
tirage = get_object_or_404(Tirage, id=tirage_id)
if timezone.now() < tirage.ouverture:
# Le tirage n'est pas encore ouvert.
opening = formats.localize(
timezone.template_localtime(tirage.ouverture))
messages.error(request, "Le tirage n'est pas encore ouvert : "
"ouverture le {:s}".format(opening))
return render(request, 'bda/resume-inscription-tirage.html', {})
if timezone.now() > tirage.fermeture:
# Le tirage est fermé.
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
choices = participant.choixspectacle_set.order_by("priority").all()
messages.error(request,
" C'est fini : tirage au sort dans la journée !")
return render(request, "bda/resume-inscription-tirage.html",
{"choices": choices})
def formfield_callback(f, **kwargs):
"""
Fonction utilisée par inlineformset_factory ci dessous.
Restreint les spectacles proposés aux spectacles du bo tirage.
"""
if f.name == "spectacle":
kwargs['queryset'] = tirage.spectacle_set
return f.formfield(**kwargs)
BdaFormSet = inlineformset_factory(
Participant,
ChoixSpectacle,
fields=("spectacle", "double_choice", "priority"),
formset=BaseBdaFormSet,
formfield_callback=formfield_callback)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
success = False
stateerror = False
if request.method == "POST":
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
stateerror = True
formset = BdaFormSet(instance=participant)
else:
formset = BdaFormSet(request.POST, instance=participant)
if formset.is_valid():
formset.save()
success = True
formset = BdaFormSet(instance=participant)
else:
formset = BdaFormSet(instance=participant)
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double:
total_price += choice.spectacle.price
# Messages
if success:
messages.success(request, "Votre inscription a été mise à jour avec "
"succès !")
if stateerror:
messages.error(request, "Impossible d'enregistrer vos modifications "
": vous avez apporté d'autres modifications "
"entre temps.")
return render(request, "bda/inscription-tirage.html",
{"formset": formset,
"total_price": total_price,
"dbstate": dbstate,
'tirage': tirage})
def do_tirage(tirage_elt, token):
"""
Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
après qu'on a vérifié que c'est légitime et que le token donné en argument
est correct.
Rend les résultats
"""
# Initialisation du dictionnaire data qui va contenir les résultats
start = time.time()
data = {
'shows': tirage_elt.spectacle_set.select_related().all(),
'token': token,
'members': tirage_elt.participant_set.all(),
'total_slots': 0,
'total_losers': 0,
'total_sold': 0,
'total_deficit': 0,
'opera_deficit': 0,
}
# On lance le tirage
choices = (
ChoixSpectacle.objects
.filter(spectacle__tirage=tirage_elt)
.order_by('participant', 'priority')
.select_related().all()
)
results = Algorithm(data['shows'], data['members'], choices)(token)
# On compte les places attribuées et les déçus
for (_, members, losers) in results:
data['total_slots'] += len(members)
data['total_losers'] += len(losers)
# On calcule le déficit et les bénéfices pour le BdA
# FIXME: le traitement de l'opéra est sale
for (show, members, _) in results:
deficit = (show.slots - len(members)) * show.price
data['total_sold'] += show.slots * show.price
if deficit >= 0:
if "Opéra" in show.location.name:
data['opera_deficit'] += deficit
data['total_deficit'] += deficit
data["total_sold"] -= data['total_deficit']
# Participant objects are not shared accross spectacle results,
# so assign a single object for each Participant id
members_uniq = {}
members2 = {}
for (show, members, _) in results:
for (member, _, _, _) in members:
if member.id not in members_uniq:
members_uniq[member.id] = member
members2[member] = []
member.total = 0
member = members_uniq[member.id]
members2[member].append(show)
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
# ---
# À partir d'ici, le tirage devient effectif
# ---
# On suppression les vieilles attributions, on sauvegarde le token et on
# désactive le tirage
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
token)
tirage_elt.enable_do_tirage = False
tirage_elt.save()
# On enregistre les nouvelles attributions
Attribution.objects.bulk_create([
Attribution(spectacle=show, participant=member)
for show, members, _ in results
for member, _, _, _ in members
])
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
for (show, _, losers) in results:
for (loser, _, _, _) in losers:
loser.choicesrevente.add(show)
loser.save()
data["duration"] = time.time() - start
data["results"] = results
return data
@buro_required
def tirage(request, tirage_id):
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
if not (tirage_elt.enable_do_tirage
and tirage_elt.fermeture < timezone.now()):
return render(request, "tirage-failed.html", {'tirage': tirage_elt})
if request.POST:
form = TokenForm(request.POST)
if form.is_valid():
results = do_tirage(tirage_elt, form.cleaned_data['token'])
return render(request, "bda-attrib-extra.html", results)
else:
form = TokenForm()
return render(request, "bda-token.html", {"form": form})
@login_required
def revente(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
if not participant.paid:
return render(request, "bda-notpaid.html", {})
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
soldform = SoldForm(participant, prefix='sold')
if request.method == 'POST':
# On met en vente une place
if 'resell' in request.POST:
resellform = ResellForm(participant, request.POST, prefix='resell')
if resellform.is_valid():
datatuple = []
attributions = resellform.cleaned_data["attributions"]
with transaction.atomic():
for attribution in attributions:
revente, created = \
SpectacleRevente.objects.get_or_create(
attribution=attribution,
defaults={'seller': participant})
if not created:
revente.seller = participant
revente.date = timezone.now()
revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
revente.shotgun = False
context = {
'vendeur': participant.user,
'show': attribution.spectacle,
'revente': revente
}
datatuple.append((
'bda-revente-new', context,
settings.MAIL_DATA['revente']['FROM'],
[participant.user.email]
))
revente.save()
send_mass_custom_mail(datatuple)
# On annule une revente
elif 'annul' in request.POST:
annulform = AnnulForm(participant, request.POST, prefix='annul')
if annulform.is_valid():
attributions = annulform.cleaned_data["attributions"]
for attribution in attributions:
attribution.revente.delete()
# On confirme une vente en transférant la place à la personne qui a
# gagné le tirage
elif 'transfer' in request.POST:
soldform = SoldForm(participant, request.POST, prefix='sold')
if soldform.is_valid():
attributions = soldform.cleaned_data['attributions']
for attribution in attributions:
attribution.participant = attribution.revente.soldTo
attribution.save()
# On annule la revente après le tirage au sort (par exemple si
# la personne qui a gagné le tirage ne se manifeste pas). La place est
# alors remise en vente
elif 'reinit' in request.POST:
soldform = SoldForm(participant, request.POST, prefix='sold')
if soldform.is_valid():
attributions = soldform.cleaned_data['attributions']
for attribution in attributions:
if attribution.spectacle.date > timezone.now():
revente = attribution.revente
revente.date = timezone.now() - timedelta(minutes=65)
revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
revente.shotgun = False
if revente.answered_mail:
revente.answered_mail.clear()
revente.save()
overdue = participant.attribution_set.filter(
spectacle__date__gte=timezone.now(),
revente__isnull=False,
revente__seller=participant,
revente__notif_sent=True)\
.filter(
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
return render(request, "bda/reventes.html",
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
"annulform": annulform, "resellform": resellform})
@login_required
def revente_interested(request, revente_id):
revente = get_object_or_404(SpectacleRevente, id=revente_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=revente.attribution.spectacle.tirage)
if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun:
return render(request, "bda-wrongtime.html",
{"revente": revente})
revente.answered_mail.add(participant)
return render(request, "bda-interested.html",
{"spectacle": revente.attribution.spectacle,
"date": revente.date_tirage})
@login_required
def list_revente(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
deja_revente = False
success = False
inscrit_revente = []
if request.method == 'POST':
form = InscriptionReventeForm(tirage, request.POST)
if form.is_valid():
choices = form.cleaned_data['spectacles']
participant.choicesrevente = choices
participant.save()
for spectacle in choices:
qset = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle)
if qset.filter(shotgun=True, soldTo__isnull=True).exists():
# Une place est disponible au shotgun, on suggère à
# l'utilisateur d'aller la récupérer
deja_revente = True
else:
# La place n'est pas disponible au shotgun, si des reventes
# pour ce spectacle existent déjà, on inscrit la personne à
# la revente ayant le moins d'inscrits
min_resell = (
qset.filter(shotgun=False)
.annotate(nb_subscribers=Count('answered_mail'))
.order_by('nb_subscribers')
.first()
)
if min_resell is not None:
min_resell.answered_mail.add(participant)
min_resell.save()
inscrit_revente.append(spectacle)
success = True
else:
form = InscriptionReventeForm(
tirage,
initial={'spectacles': participant.choicesrevente.all()}
)
# Messages
if success:
messages.success(request, "Ton inscription a bien été prise en compte")
if deja_revente:
messages.info(request, "Des reventes existent déjà pour certains de "
"ces spectacles, vérifie les places "
"disponibles sans tirage !")
if inscrit_revente:
shows = map("<li>{!s}</li>".format, inscrit_revente)
msg = (
"Tu as été inscrit à des reventes en cours pour les spectacles "
"<ul>{:s}</ul>".format('\n'.join(shows))
)
messages.info(request, msg, extra_tags="safe")
return render(request, "bda/liste-reventes.html", {"form": form})
@login_required
def buy_revente(request, spectacle_id):
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
tirage = spectacle.tirage
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
reventes = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle,
soldTo__isnull=True)
# Si l'utilisateur veut racheter une place qu'il est en train de revendre,
# on supprime la revente en question.
if reventes.filter(seller=participant).exists():
revente = reventes.filter(seller=participant)[0]
revente.delete()
return HttpResponseRedirect(reverse("bda-shotgun",
args=[tirage.id]))
reventes_shotgun = list(reventes.filter(shotgun=True).all())
if not reventes_shotgun:
return render(request, "bda-no-revente.html", {})
if request.POST:
revente = random.choice(reventes_shotgun)
revente.soldTo = participant
revente.save()
context = {
'show': spectacle,
'acheteur': request.user,
'vendeur': revente.seller.user
}
send_custom_mail(
'bda-buy-shotgun',
'bda@ens.fr',
[revente.seller.user.email],
context=context,
)
return render(request, "bda-success.html",
{"seller": revente.attribution.participant.user,
"spectacle": spectacle})
return render(request, "revente-confirm.html",
{"spectacle": spectacle,
"user": request.user})
@login_required
def revente_shotgun(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
spectacles = tirage.spectacle_set.filter(
date__gte=timezone.now())
shotgun = []
for spectacle in spectacles:
reventes = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle,
shotgun=True,
soldTo__isnull=True)
if reventes.exists():
shotgun.append(spectacle)
return render(request, "bda-shotgun.html",
{"shotgun": shotgun})
@buro_required
def spectacle(request, tirage_id, spectacle_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage)
attributions = spectacle.attribues.all()
participants = {}
for attrib in attributions:
participant = attrib.participant
participant_info = {'lastname': participant.user.last_name,
'name': participant.user.get_full_name,
'username': participant.user.username,
'email': participant.user.email,
'given': int(attrib.given),
'paid': participant.paid,
'nb_places': 1}
if participant.id in participants:
participants[participant.id]['nb_places'] += 1
participants[participant.id]['given'] += attrib.given
else:
participants[participant.id] = participant_info
participants_info = sorted(participants.values(),
key=lambda part: part['lastname'])
return render(request, "bda-participants.html",
{"spectacle": spectacle, "participants": participants_info})
class SpectacleListView(ListView):
model = Spectacle
template_name = 'spectacle_list.html'
def get_queryset(self):
self.tirage = get_object_or_404(Tirage, id=self.kwargs['tirage_id'])
categories = self.tirage.spectacle_set.all()
return categories
def get_context_data(self, **kwargs):
context = super(SpectacleListView, self).get_context_data(**kwargs)
context['tirage_id'] = self.tirage.id
context['tirage_name'] = self.tirage.title
return context
@buro_required
def unpaid(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
unpaid = tirage.participant_set \
.annotate(nb_attributions=Count('attribution')) \
.filter(paid=False, nb_attributions__gt=0).all()
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
@buro_required
def send_rappel(request, spectacle_id):
show = get_object_or_404(Spectacle, id=spectacle_id)
# Mails d'exemples
exemple_mail_1place = render_custom_mail('bda-rappel', {
'member': request.user,
'show': show,
'nb_attr': 1
})
exemple_mail_2places = render_custom_mail('bda-rappel', {
'member': request.user,
'show': show,
'nb_attr': 2
})
# Contexte
ctxt = {'show': show,
'exemple_mail_1place': exemple_mail_1place,
'exemple_mail_2places': exemple_mail_2places}
# Envoi confirmé
if request.method == 'POST':
members = show.send_rappel()
ctxt['sent'] = True
ctxt['members'] = members
# Demande de confirmation
else:
ctxt['sent'] = False
return render(request, "bda/mails-rappel.html", ctxt)
def descriptions_spectacles(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
shows_qs = tirage.spectacle_set
category_name = request.GET.get('category', '')
location_id = request.GET.get('location', '')
if category_name:
shows_qs = shows_qs.filter(category__name=category_name)
if location_id:
try:
shows_qs = shows_qs.filter(location__id=int(location_id))
except ValueError:
return HttpResponseBadRequest(
"La variable GET 'location' doit contenir un entier")
return render(request, 'descriptions.html', {'shows': shows_qs.all()})
def catalogue(request, request_type):
"""
Vue destinée à communiquer avec un client AJAX, fournissant soit :
- la liste des tirages
- les catégories et salles d'un tirage
- les descriptions d'un tirage (filtrées selon la catégorie et la salle)
"""
if request_type == "list":
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
data_return = list(
Tirage.objects.filter(appear_catalogue=True).values('id', 'title')
)
return JsonResponse(data_return, safe=False)
if request_type == "details":
# Dans ce cas on retourne une liste des catégories et des salles
tirage_id = request.GET.get('id', None)
if tirage_id is None:
return HttpResponseBadRequest(
"Missing GET parameter: id <int>"
)
try:
tirage = get_object_or_404(Tirage, id=int(tirage_id))
except ValueError:
return HttpResponseBadRequest(
"Bad format: int expected for `id`"
)
shows = tirage.spectacle_set.values_list("id", flat=True)
categories = list(
CategorieSpectacle.objects
.filter(spectacle__in=shows)
.distinct()
.values('id', 'name')
)
locations = list(
Salle.objects
.filter(spectacle__in=shows)
.distinct()
.values('id', 'name')
)
data_return = {'categories': categories, 'locations': locations}
return JsonResponse(data_return, safe=False)
if request_type == "descriptions":
# Ici on retourne les descriptions correspondant à la catégorie et
# à la salle spécifiées
tirage_id = request.GET.get('id', '')
categories = request.GET.get('category', '[]')
locations = request.GET.get('location', '[]')
try:
tirage_id = int(tirage_id)
categories_id = json.loads(categories)
locations_id = json.loads(locations)
# Integers expected
if not all(isinstance(id, int) for id in categories_id):
raise ValueError
if not all(isinstance(id, int) for id in locations_id):
raise ValueError
except ValueError: # Contient JSONDecodeError
return HttpResponseBadRequest(
"Parse error, please ensure the GET parameters have the "
"following types:\n"
"id: int, category: [int], location: [int]\n"
"Data received:\n"
"id = {}, category = {}, locations = {}"
.format(request.GET.get('id', ''),
request.GET.get('category', '[]'),
request.GET.get('location', '[]'))
)
tirage = get_object_or_404(Tirage, id=tirage_id)
shows_qs = tirage.spectacle_set
if categories_id:
shows_qs = shows_qs.filter(category__id__in=categories_id)
if locations_id:
shows_qs = shows_qs.filter(location__id__in=locations_id)
# On convertit les descriptions à envoyer en une liste facilement
# JSONifiable (il devrait y avoir un moyen plus efficace en
# redéfinissant le serializer de JSON)
data_return = [{
'title': spectacle.title,
'category': str(spectacle.category),
'date': str(formats.date_format(
timezone.localtime(spectacle.date),
"SHORT_DATETIME_FORMAT")),
'location': str(spectacle.location),
'vips': spectacle.vips,
'description': spectacle.description,
'slots_description': spectacle.slots_description,
'quotes': list(Quote.objects.filter(spectacle=spectacle).values(
'author', 'text')),
'image': spectacle.getImgUrl(),
'ext_link': spectacle.ext_link,
'price': spectacle.price,
'slots': spectacle.slots
}
for spectacle in shows_qs.all()
]
return JsonResponse(data_return, safe=False)
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
return HttpResponseBadRequest()

View file

@ -1,7 +0,0 @@
import os
from channels.asgi import get_channel_layer
if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
channel_layer = get_channel_layer()

View file

View file

@ -1,9 +0,0 @@
# -*- encoding: utf-8 -*-
"""
Formats français.
"""
from __future__ import unicode_literals
DATETIME_FORMAT = r'l j F Y \à H:i'

View file

@ -1,3 +0,0 @@
from kfet.routing import channel_routing as kfet_channel_routings
channel_routing = kfet_channel_routings

View file

@ -1 +0,0 @@
secret.py

View file

@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
"""
Django common settings for cof project.
Everything which is supposed to be identical between the production server and
the local development server should be here.
"""
import os
# Database credentials
try:
from .secret import DBNAME, DBUSER, DBPASSWD
except ImportError:
# On the local development VM, theses credentials are in the environment
DBNAME = os.environ["DBNAME"]
DBUSER = os.environ["DBUSER"]
DBPASSWD = os.environ["DBPASSWD"]
except KeyError:
raise RuntimeError("Secrets missing")
# Other secrets
try:
from .secret import (
SECRET_KEY, RECAPTCHA_PUBLIC_KEY, RECAPTCHA_PRIVATE_KEY, ADMINS
)
except ImportError:
raise RuntimeError("Secrets missing")
BASE_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
# Application definition
INSTALLED_APPS = [
'gestioncof',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'grappelli',
'django.contrib.admin',
'django.contrib.admindocs',
'bda',
'autocomplete_light',
'captcha',
'django_cas_ng',
'bootstrapform',
'kfet',
'channels',
'widget_tweaks',
'custommail',
'djconfig',
]
MIDDLEWARE_CLASSES = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'kfet.middleware.KFetAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'djconfig.middleware.DjConfigMiddleware',
]
ROOT_URLCONF = 'cof.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'djconfig.context_processors.config',
'gestioncof.shared.context_processor',
'kfet.context_processors.auth',
'kfet.context_processors.config',
],
},
},
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': DBNAME,
'USER': DBUSER,
'PASSWORD': DBPASSWD,
'HOST': os.environ.get('DBHOST', 'localhost'),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'fr-fr'
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Various additional settings
SITE_ID = 1
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
MAIL_DATA = {
'petits_cours': {
'FROM': "Le COF <cof@ens.fr>",
'BCC': "archivescof@gmail.com",
'REPLYTO': "cof@ens.fr"},
'rappels': {
'FROM': 'Le BdA <bda@ens.fr>',
'REPLYTO': 'Le BdA <bda@ens.fr>'},
'revente': {
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
}
LOGIN_URL = "cof-login"
LOGIN_REDIRECT_URL = "home"
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
CAS_IGNORE_REFERER = True
CAS_REDIRECT_URL = '/'
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'gestioncof.shared.COFCASBackend',
'kfet.backends.GenericTeamBackend',
)
RECAPTCHA_USE_SSL = True
# Channels settings
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [(os.environ.get("REDIS_HOST", "localhost"), 6379)],
},
"ROUTING": "cof.routing.channel_routing",
}
}
FORMAT_MODULE_PATH = 'cof.locale'

View file

@ -1,51 +0,0 @@
"""
Django development settings for the cof project.
The settings that are not listed here are imported from .common
"""
import os
from .common import *
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEBUG = True
# ---
# Apache static/media config
# ---
STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
# ---
# Debug tool bar
# ---
def show_toolbar(request):
"""
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
machine physique n'est pas forcément connue, et peut difficilement être
mise dans les INTERNAL_IPS.
"""
if not DEBUG:
return False
if request.is_ajax():
return False
return True
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE_CLASSES = (
["debug_toolbar.middleware.DebugToolbarMiddleware"]
+ MIDDLEWARE_CLASSES
)
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}

View file

@ -1,26 +0,0 @@
"""
Django development settings for the cof project.
The settings that are not listed here are imported from .common
"""
import os
from .common import *
DEBUG = False
ALLOWED_HOSTS = [
"cof.ens.fr",
"www.cof.ens.fr",
"dev.cof.ens.fr"
]
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static")
STATIC_URL = "/gestion/static/"
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
MEDIA_URL = "/gestion/media/"
LDAP_SERVER_URL = "ldaps://ldap.spi.ens.fr:636"
EMAIL_HOST = "nef.ens.fr"

View file

@ -1,4 +0,0 @@
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
RECAPTCHA_PUBLIC_KEY = "DUMMY"
RECAPTCHA_PRIVATE_KEY = "DUMMY"
ADMINS = None

View file

@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
"""
Fichier principal de configuration des urls du projet GestioCOF
"""
import autocomplete_light
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic.base import TemplateView
from django.contrib.auth import views as django_views
from django_cas_ng import views as django_cas_views
from gestioncof import views as gestioncof_views, csv_views
from gestioncof.urls import export_patterns, petitcours_patterns, \
surveys_patterns, events_patterns, calendar_patterns, \
clubs_patterns
from gestioncof.autocomplete import autocomplete
autocomplete_light.autodiscover()
admin.autodiscover()
urlpatterns = [
# Page d'accueil
url(r'^$', gestioncof_views.home, name='home'),
# Le BdA
url(r'^bda/', include('bda.urls')),
# Les exports
url(r'^export/', include(export_patterns)),
# Les petits cours
url(r'^petitcours/', include(petitcours_patterns)),
# Les sondages
url(r'^survey/', include(surveys_patterns)),
# Evenements
url(r'^event/', include(events_patterns)),
# Calendrier
url(r'^calendar/', include(calendar_patterns)),
# Clubs
url(r'^clubs/', include(clubs_patterns)),
# Authentification
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
name="cof-denied"),
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
url(r'^cas/logout$', django_cas_views.logout),
url(r'^outsider/login$', gestioncof_views.login_ext),
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
url(r'^login$', gestioncof_views.login, name="cof-login"),
url(r'^logout$', gestioncof_views.logout),
# Infos persos
url(r'^profile$', gestioncof_views.profile),
url(r'^outsider/password-change$', django_views.password_change),
url(r'^outsider/password-change-done$',
django_views.password_change_done,
name='password_change_done'),
# Inscription d'un nouveau membre
url(r'^registration$', gestioncof_views.registration),
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
r'(?P<fullname>.*)$',
gestioncof_views.registration_form2, name="clipper-registration"),
url(r'^registration/user/(?P<username>.+)$',
gestioncof_views.registration_form2, name="user-registration"),
url(r'^registration/empty$', gestioncof_views.registration_form2,
name="empty-registration"),
# Autocompletion
url(r'^autocomplete/registration$', autocomplete),
url(r'^autocomplete/', include('autocomplete_light.urls')),
# Interface admin
url(r'^admin/logout/', gestioncof_views.logout),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
csv_views.admin_list_export,
{'fields': ['username', ]}),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
# Liens utiles du COF et du BdA
url(r'^utile_cof$', gestioncof_views.utile_cof),
url(r'^utile_bda$', gestioncof_views.utile_bda),
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
url(r'^k-fet/', include('kfet.urls')),
]
if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)),
]
# Si on est en production, MEDIA_ROOT est servi par Apache.
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)

View file

View file

@ -1,302 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \
CofProfile, EventOption, EventOptionChoice, Event, Club, \
Survey, EventCommentField, EventRegistration
from gestioncof.petits_cours_models import PetitCoursDemande, \
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
PetitCoursAttributionCounter
from django.contrib.auth.models import User, Group, Permission
from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.db.models import Q
import django.utils.six as six
import autocomplete_light
def add_link_field(target_model='', field='', link_text=six.text_type,
desc_text=six.text_type):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
if not link_obj.id:
return ""
url = reverse(reverse_path, args=(link_obj.id,))
return mark_safe("<a href='%s'>%s</a>"
% (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = desc_text(reverse_name + ' link')
cls.link = link
cls.readonly_fields =\
list(getattr(cls, 'readonly_fields', [])) + ['link']
return cls
return add_link
class SurveyQuestionAnswerInline(admin.TabularInline):
model = SurveyQuestionAnswer
@add_link_field(desc_text=lambda x: "Réponses",
link_text=lambda x: "Éditer les réponses")
class SurveyQuestionInline(admin.TabularInline):
model = SurveyQuestion
class SurveyQuestionAdmin(admin.ModelAdmin):
search_fields = ('survey__title', 'answer')
inlines = [
SurveyQuestionAnswerInline,
]
class SurveyAdmin(admin.ModelAdmin):
search_fields = ('title', 'details')
inlines = [
SurveyQuestionInline,
]
class EventOptionChoiceInline(admin.TabularInline):
model = EventOptionChoice
@add_link_field(desc_text=lambda x: "Choix",
link_text=lambda x: "Éditer les choix")
class EventOptionInline(admin.TabularInline):
model = EventOption
class EventCommentFieldInline(admin.TabularInline):
model = EventCommentField
class EventOptionAdmin(admin.ModelAdmin):
search_fields = ('event__title', 'name')
inlines = [
EventOptionChoiceInline,
]
class EventAdmin(admin.ModelAdmin):
search_fields = ('title', 'location', 'description')
inlines = [
EventOptionInline,
EventCommentFieldInline,
]
class CofProfileInline(admin.StackedInline):
model = CofProfile
inline_classes = ("collapse open",)
class FkeyLookup(object):
def __init__(self, fkeydecl, short_description=None,
admin_order_field=None):
self.fk, fkattrs = fkeydecl.split('__', 1)
self.fkattrs = fkattrs.split('__')
self.short_description = short_description or self.fkattrs[-1]
self.admin_order_field = admin_order_field or fkeydecl
def __get__(self, obj, klass):
if obj is None:
"""
hack required to make Django validate (if obj is
None, then we're a class, and classes are callable
<wink>)
"""
return self
item = getattr(obj, self.fk)
for attr in self.fkattrs:
item = getattr(item, attr)
return item
def ProfileInfo(field, short_description, boolean=False):
def getter(self):
try:
return getattr(self.profile, field)
except CofProfile.DoesNotExist:
return ""
getter.short_description = short_description
getter.boolean = boolean
return getter
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
"Login clipper")
User.profile_num = FkeyLookup("profile__num", "Numéro")
User.profile_phone = ProfileInfo("phone", "Téléphone")
User.profile_occupation = ProfileInfo("occupation", "Occupation")
User.profile_departement = ProfileInfo("departement", "Departement")
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
"ML BdA-R", True)
class UserProfileAdmin(UserAdmin):
def is_buro(self, obj):
try:
return obj.profile.is_buro
except CofProfile.DoesNotExist:
return False
is_buro.short_description = 'Membre du Buro'
is_buro.boolean = True
def is_cof(self, obj):
try:
return obj.profile.is_cof
except CofProfile.DoesNotExist:
return False
is_cof.short_description = 'Membre du COF'
is_cof.boolean = True
list_display = ('profile_num',) + UserAdmin.list_display \
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
'profile_mailing_cof', 'profile_mailing_bda',
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
list_display_links = ('username', 'email', 'first_name', 'last_name')
list_filter = UserAdmin.list_filter \
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
'profile__mailing_bda')
search_fields = UserAdmin.search_fields + ('profile__phone',)
inlines = [
CofProfileInline,
]
staff_fieldsets = [
(None, {'fields': ['username', 'password']}),
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
]
def get_fieldsets(self, request, user=None):
if not request.user.is_superuser:
return self.staff_fieldsets
return super(UserProfileAdmin, self).get_fieldsets(request, user)
def save_model(self, request, user, form, change):
cof_group, created = Group.objects.get_or_create(name='COF')
if created:
# Si le groupe COF n'était pas déjà dans la bdd
# On lui assigne les bonnes permissions
perms = Permission.objects.filter(
Q(content_type__app_label='gestioncof')
| Q(content_type__app_label='bda')
| (Q(content_type__app_label='auth')
& Q(content_type__model='user')))
cof_group.permissions = perms
# On y associe les membres du Burô
cof_group.user_set = User.objects.filter(profile__is_buro=True)
# Sauvegarde
cof_group.save()
# le Burô est staff et appartient au groupe COF
if user.profile.is_buro:
user.is_staff = True
user.groups.add(cof_group)
else:
user.is_staff = False
user.groups.remove(cof_group)
user.save()
# FIXME: This is absolutely horrible.
def user_unicode(self):
if self.first_name and self.last_name:
return "%s %s (%s)" % (self.first_name, self.last_name, self.username)
else:
return self.username
if six.PY2:
User.__unicode__ = user_unicode
else:
User.__str__ = user_unicode
class EventRegistrationAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
list_display = ('__unicode__' if six.PY2 else '__str__', 'event', 'user',
'paid')
list_filter = ('paid',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'event__title')
class PetitCoursAbilityAdmin(admin.ModelAdmin):
list_display = ('user', 'matiere', 'niveau', 'agrege')
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'matiere__name', 'niveau')
list_filter = ('matiere', 'niveau', 'agrege')
class PetitCoursAttributionAdmin(admin.ModelAdmin):
list_display = ('user', 'demande', 'matiere', 'rank', )
search_fields = ('user__username', 'matiere__name')
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
list_display = ('user', 'matiere', 'count', )
list_filter = ('matiere',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'matiere__name')
actions = ['reset', ]
actions_on_bottom = True
def reset(self, request, queryset):
queryset.update(count=0)
reset.short_description = "Remise à zéro du compteur"
class PetitCoursDemandeAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
'traitee', 'processed')
list_filter = ('traitee', 'niveau')
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
class ClubAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = super(ClubAdminForm, self).clean()
respos = cleaned_data.get('respos')
members = cleaned_data.get('membres')
for respo in respos.all():
if respo not in members:
raise forms.ValidationError(
"Erreur : le respo %s n'est pas membre du club."
% respo.get_full_name())
return cleaned_data
class ClubAdmin(admin.ModelAdmin):
list_display = ['name']
form = ClubAdminForm
admin.site.register(Survey, SurveyAdmin)
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
admin.site.register(Event, EventAdmin)
admin.site.register(EventOption, EventOptionAdmin)
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)
admin.site.register(CofProfile)
admin.site.register(Club, ClubAdmin)
admin.site.register(PetitCoursSubject)
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
admin.site.register(PetitCoursAttributionCounter,
PetitCoursAttributionCounterAdmin)
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
admin.site.register(EventRegistration, EventRegistrationAdmin)

View file

@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
from ldap3 import Connection
from django import shortcuts
from django.http import Http404
from django.db.models import Q
from django.contrib.auth.models import User
from django.conf import settings
from gestioncof.models import CofProfile
from gestioncof.decorators import buro_required
class Clipper(object):
def __init__(self, clipper, fullname):
if fullname is None:
fullname = ""
assert isinstance(clipper, str)
assert isinstance(fullname, str)
self.clipper = clipper
self.fullname = fullname
@buro_required
def autocomplete(request):
if "q" not in request.GET:
raise Http404
q = request.GET['q']
data = {
'q': q,
}
queries = {}
bits = q.split()
# Fetching data from User and CofProfile tables
queries['members'] = CofProfile.objects.filter(is_cof=True)
queries['users'] = User.objects.filter(profile__is_cof=False)
for bit in bits:
queries['members'] = queries['members'].filter(
Q(user__first_name__icontains=bit)
| Q(user__last_name__icontains=bit)
| Q(user__username__icontains=bit)
| Q(login_clipper__icontains=bit))
queries['users'] = queries['users'].filter(
Q(first_name__icontains=bit)
| Q(last_name__icontains=bit)
| Q(username__icontains=bit))
queries['members'] = queries['members'].distinct()
queries['users'] = queries['users'].distinct()
# Clearing redundancies
usernames = (
set(queries['members'].values_list('login_clipper', flat='True'))
| set(queries['users'].values_list('profile__login_clipper',
flat='True'))
)
# Fetching data from the SPI
if hasattr(settings, 'LDAP_SERVER_URL'):
# Fetching
ldap_query = '(&{:s})'.format(''.join(
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
for bit in bits if bit.isalnum()
))
if ldap_query != "(&)":
# If none of the bits were legal, we do not perform the query
entries = None
with Connection(settings.LDAP_SERVER_URL) as conn:
conn.search(
'dc=spi,dc=ens,dc=fr', ldap_query,
attributes=['uid', 'cn']
)
entries = conn.entries
# Clearing redundancies
queries['clippers'] = [
Clipper(entry.uid.value, entry.cn.value)
for entry in entries
if entry.uid.value
and entry.uid.value not in usernames
]
# Resulting data
data.update(queries)
data['options'] = sum(len(query) for query in queries)
return shortcuts.render(request, "autocomplete_user.html", data)

View file

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
import autocomplete_light
from django.contrib.auth.models import User
autocomplete_light.register(
User, search_fields=('username', 'first_name', 'last_name'),
attrs={'placeholder': 'membre...'}
)

View file

@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import csv
from django.http import HttpResponse, HttpResponseForbidden
from django.template.defaultfilters import slugify
from django.apps import apps
def export(qs, fields=None):
model = qs.model
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=%s.csv' \
% slugify(model.__name__)
writer = csv.writer(response)
# Write headers to CSV file
if fields:
headers = fields
else:
headers = []
for field in model._meta.fields:
headers.append(field.name)
writer.writerow(headers)
# Write data to CSV file
for obj in qs:
row = []
for field in headers:
if field in headers:
val = getattr(obj, field)
if callable(val):
val = val()
row.append(val)
writer.writerow(row)
# Return CSV file to browser as download
return response
def admin_list_export(request, model_name, app_label, queryset=None,
fields=None, list_display=True):
"""
Put the following line in your urls.py BEFORE your admin include
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
'util.csv_view.admin_list_export'),
"""
if not request.user.is_staff:
return HttpResponseForbidden()
if not queryset:
model = apps.get_model(app_label, model_name)
queryset = model.objects.all()
queryset = queryset.filter(profile__is_cof=True)
if not fields:
if list_display and len(queryset.model._meta.admin.list_display) > 1:
fields = queryset.model._meta.admin.list_display
else:
fields = None
return export(queryset, fields)
"""
Create your own change_list.html for your admin view and put something
like this in it:
{% block object-tools %}
<ul class="object-tools">
<li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}
{%endif%}" class="addlink">Export to CSV</a></li>
{% if has_add_permission %}
<li><a href="add/{% if is_popup %}?_popup=1{% endif %}"
class="addlink">
{% blocktrans with cl.opts.verbose_name|escape as name %}
Add {{ name }}{% endblocktrans %}</a></li>
{% endif %}
</ul>
{% endblock %}
"""

View file

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.decorators import user_passes_test
def is_cof(user):
try:
profile = user.profile
return profile.is_cof
except:
return False
cof_required = user_passes_test(is_cof)
def is_buro(user):
try:
profile = user.profile
return profile.is_buro
except:
return False
buro_required = user_passes_test(is_buro)

View file

@ -1,199 +0,0 @@
[
{
"fields": {
"old": false,
"details": "Il nous casse les oreilles, qu'est ce qu'on en fait\u00a0?",
"survey_open": true,
"title": "Sort du barde"
},
"model": "gestioncof.survey",
"pk": 1
},
{
"fields": {
"question": "Sanction s'il chante",
"survey": 1,
"multi_answers": true
},
"model": "gestioncof.surveyquestion",
"pk": 1
},
{
"fields": {
"question": "Est-ce qu'on le garde\u00a0?",
"survey": 1,
"multi_answers": false
},
"model": "gestioncof.surveyquestion",
"pk": 2
},
{
"fields": {
"answer": "On l'ernestise",
"survey_question": 1
},
"model": "gestioncof.surveyquestionanswer",
"pk": 1
},
{
"fields": {
"answer": "On ligote",
"survey_question": 1
},
"model": "gestioncof.surveyquestionanswer",
"pk": 2
},
{
"fields": {
"answer": "On le prive de banquet",
"survey_question": 1
},
"model": "gestioncof.surveyquestionanswer",
"pk": 3
},
{
"fields": {
"answer": "Oui",
"survey_question": 2
},
"model": "gestioncof.surveyquestionanswer",
"pk": 4
},
{
"fields": {
"answer": "Non",
"survey_question": 2
},
"model": "gestioncof.surveyquestionanswer",
"pk": 5
},
{
"fields": {
"old": false,
"description": "On va casser du romain.",
"end_date": "2016-09-12T00:00:00Z",
"title": "Bataille de Gergovie",
"image": "",
"location": "Gergovie",
"registration_open": true,
"start_date": "2016-09-09T00:00:00Z"
},
"model": "gestioncof.event",
"pk": 1
},
{
"fields": {
"default": "",
"event": 1,
"fieldtype": "text",
"name": "Commentaires"
},
"model": "gestioncof.eventcommentfield",
"pk": 1
},
{
"fields": {
"multi_choices": true,
"event": 1,
"name": "Potion magique"
},
"model": "gestioncof.eventoption",
"pk": 1
},
{
"fields": {
"event_option": 1,
"value": "Je suis alergique"
},
"model": "gestioncof.eventoptionchoice",
"pk": 1
},
{
"fields": {
"event_option": 1,
"value": "J'en veux"
},
"model": "gestioncof.eventoptionchoice",
"pk": 2
},
{
"fields": {
"event_option": 1,
"value": "Je suis tomb\u00e9 dans la marmite quand j'\u00e9tais petit"
},
"model": "gestioncof.eventoptionchoice",
"pk": 3
},
{
"fields": {
"name": "Bagarre"
},
"model": "gestioncof.petitcourssubject",
"pk": 1
},
{
"fields": {
"name": "Lancer de menhir"
},
"model": "gestioncof.petitcourssubject",
"pk": 2
},
{
"fields": {
"name": "Pr\u00e9paration de potions"
},
"model": "gestioncof.petitcourssubject",
"pk": 3
},
{
"fields": {
"name": "Chant"
},
"model": "gestioncof.petitcourssubject",
"pk": 4
},
{
"fields": {
"traitee": false,
"remarques": "En grande difficult\u00e9",
"quand": "weekend (dimanche) / soir apr\u00e8s les cours",
"name": "Jules C\u00e9sar",
"created": "2016-07-15T11:12:35Z",
"niveau": "prepa1styear",
"agrege_requis": false,
"phone": "",
"traitee_par": null,
"matieres": [
1
],
"lieu": "Al\u00e9sia",
"freq": "3 fois / semaine",
"email": "jules.cesar@polytechnique.edu",
"processed": null
},
"model": "gestioncof.petitcoursdemande",
"pk": 1
},
{
"fields": {
"traitee": false,
"remarques": "",
"quand": "Weekends",
"name": "Jules C\u00e9sar",
"created": "2016-07-15T11:13:26Z",
"niveau": "lycee",
"agrege_requis": true,
"phone": "",
"traitee_par": null,
"matieres": [
3
],
"lieu": "\u00e0 domicile",
"freq": "toutes les semaines",
"email": "jules.cesar@polytechnique.edu",
"processed": null
},
"model": "gestioncof.petitcoursdemande",
"pk": 2
}
]

View file

@ -1,10 +0,0 @@
[
{
"fields": {
"domain": "localhost",
"name": "GestioCOF - dev - local"
},
"model": "sites.site",
"pk": 1
}
]

View file

@ -1,405 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.forms.formsets import BaseFormSet, formset_factory
from django.db.models import Max
from django.core.validators import MinLengthValidator
from gestioncof.models import CofProfile, EventCommentValue, \
CalendarSubscription, Club
from gestioncof.widgets import TriStateCheckbox
from gestioncof.shared import lock_table, unlock_table
from bda.models import Spectacle
class EventForm(forms.Form):
def __init__(self, *args, **kwargs):
event = kwargs.pop("event")
self.event = event
current_choices = kwargs.pop("current_choices", None)
super(EventForm, self).__init__(*args, **kwargs)
choices = {}
if current_choices:
for choice in current_choices.all():
if choice.event_option.id not in choices:
choices[choice.event_option.id] = [choice.id]
else:
choices[choice.event_option.id].append(choice.id)
all_choices = choices
for option in event.options.all():
choices = [(choice.id, choice.value)
for choice in option.choices.all()]
if option.multi_choices:
initial = [] if option.id not in all_choices \
else all_choices[option.id]
field = forms.MultipleChoiceField(
label=option.name,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if option.id not in all_choices \
else all_choices[option.id][0]
field = forms.ChoiceField(label=option.name,
choices=choices,
widget=RadioSelect,
required=False,
initial=initial)
field.option_id = option.id
self.fields["option_%d" % option.id] = field
def choices(self):
for name, value in self.cleaned_data.items():
if name.startswith('option_'):
yield (self.fields[name].option_id, value)
class SurveyForm(forms.Form):
def __init__(self, *args, **kwargs):
survey = kwargs.pop("survey")
current_answers = kwargs.pop("current_answers", None)
super(SurveyForm, self).__init__(*args, **kwargs)
answers = {}
if current_answers:
for answer in current_answers.all():
if answer.survey_question.id not in answers:
answers[answer.survey_question.id] = [answer.id]
else:
answers[answer.survey_question.id].append(answer.id)
for question in survey.questions.all():
choices = [(answer.id, answer.answer)
for answer in question.answers.all()]
if question.multi_answers:
initial = [] if question.id not in answers\
else answers[question.id]
field = forms.MultipleChoiceField(
label=question.question,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if question.id not in answers\
else answers[question.id][0]
field = forms.ChoiceField(label=question.question,
choices=choices,
widget=RadioSelect,
required=False,
initial=initial)
field.question_id = question.id
self.fields["question_%d" % question.id] = field
def answers(self):
for name, value in self.cleaned_data.items():
if name.startswith('question_'):
yield (self.fields[name].question_id, value)
class SurveyStatusFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
survey = kwargs.pop("survey")
super(SurveyStatusFilterForm, self).__init__(*args, **kwargs)
for question in survey.questions.all():
for answer in question.answers.all():
name = "question_%d_answer_%d" % (question.id, answer.id)
if self.is_bound \
and self.data.get(self.add_prefix(name), None):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(
label="%s : %s" % (question.question, answer.answer),
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
field.question_id = question.id
field.answer_id = answer.id
self.fields[name] = field
def filters(self):
for name, value in self.cleaned_data.items():
if name.startswith('question_'):
yield (self.fields[name].question_id,
self.fields[name].answer_id, value)
class EventStatusFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
event = kwargs.pop("event")
super(EventStatusFilterForm, self).__init__(*args, **kwargs)
for option in event.options.all():
for choice in option.choices.all():
name = "option_%d_choice_%d" % (option.id, choice.id)
if self.is_bound \
and self.data.get(self.add_prefix(name), None):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(
label="%s : %s" % (option.name, choice.value),
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
field.option_id = option.id
field.choice_id = choice.id
self.fields[name] = field
# has_paid
name = "event_has_paid"
if self.is_bound and self.data.get(self.add_prefix(name), None):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(label="Événement payé",
choices=[("yes", "yes"), ("no", "no"),
("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
self.fields[name] = field
def filters(self):
for name, value in self.cleaned_data.items():
if name.startswith('option_'):
yield (self.fields[name].option_id,
self.fields[name].choice_id, value)
elif name == "event_has_paid":
yield ("has_paid", None, value)
class UserProfileForm(forms.ModelForm):
first_name = forms.CharField(label=_('Prénom'), max_length=30)
last_name = forms.CharField(label=_('Nom'), max_length=30)
def __init__(self, *args, **kw):
super(UserProfileForm, self).__init__(*args, **kw)
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
def save(self, *args, **kw):
super(UserProfileForm, self).save(*args, **kw)
self.instance.user.first_name = self.cleaned_data.get('first_name')
self.instance.user.last_name = self.cleaned_data.get('last_name')
self.instance.user.save()
class Meta:
model = CofProfile
fields = ["first_name", "last_name", "phone", "mailing_cof",
"mailing_bda", "mailing_bda_revente"]
class RegistrationUserForm(forms.ModelForm):
def __init__(self, *args, **kw):
super(RegistrationUserForm, self).__init__(*args, **kw)
self.fields['username'].help_text = ""
def force_long_username(self):
self.fields['username'].validators = [MinLengthValidator(9)]
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email")
class RegistrationPassUserForm(RegistrationUserForm):
"""
Formulaire pour changer le mot de passe d'un utilisateur.
"""
password1 = forms.CharField(label=_('Mot de passe'),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Confirmation du mot de passe'),
widget=forms.PasswordInput)
def clean_password2(self):
pass1 = self.cleaned_data['password1']
pass2 = self.cleaned_data['password2']
if pass1 and pass2:
if pass1 != pass2:
raise forms.ValidationError(_('Mots de passe non identiques.'))
return pass2
def save(self, commit=True, *args, **kwargs):
user = super(RegistrationPassUserForm, self).save(commit, *args,
**kwargs)
user.set_password(self.cleaned_data['password2'])
if commit:
user.save()
return user
class RegistrationProfileForm(forms.ModelForm):
def __init__(self, *args, **kw):
super(RegistrationProfileForm, self).__init__(*args, **kw)
self.fields['mailing_cof'].initial = True
self.fields['mailing_bda'].initial = True
self.fields['mailing_bda_revente'].initial = True
self.fields['num'].widget.attrs['readonly'] = True
self.fields.keyOrder = [
'login_clipper',
'phone',
'occupation',
'departement',
'is_cof',
'num',
'type_cotiz',
'mailing_cof',
'mailing_bda',
'mailing_bda_revente',
'comments'
]
def save(self, *args, **kw):
instance = super(RegistrationProfileForm, self).save(*args, **kw)
if instance.is_cof and not instance.num:
# Generate new number
try:
lock_table(CofProfile)
aggregate = CofProfile.objects.aggregate(Max('num'))
instance.num = aggregate['num__max'] + 1
instance.save()
self.cleaned_data['num'] = instance.num
self.data['num'] = instance.num
finally:
unlock_table(CofProfile)
return instance
class Meta:
model = CofProfile
fields = ("login_clipper", "num", "phone", "occupation",
"departement", "is_cof", "type_cotiz", "mailing_cof",
"mailing_bda", "mailing_bda_revente", "comments")
STATUS_CHOICES = (('no', 'Non'),
('wait', 'Oui mais attente paiement'),
('paid', 'Oui payé'),)
class AdminEventForm(forms.Form):
status = forms.ChoiceField(label="Inscription", initial="no",
choices=STATUS_CHOICES, widget=RadioSelect)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop("event")
registration = kwargs.pop("current_registration", None)
current_choices, paid = \
(registration.options.all(), registration.paid) \
if registration is not None else ([], None)
if paid is True:
kwargs["initial"] = {"status": "paid"}
elif paid is False:
kwargs["initial"] = {"status": "wait"}
else:
kwargs["initial"] = {"status": "no"}
super(AdminEventForm, self).__init__(*args, **kwargs)
choices = {}
for choice in current_choices:
if choice.event_option.id not in choices:
choices[choice.event_option.id] = [choice.id]
else:
choices[choice.event_option.id].append(choice.id)
all_choices = choices
for option in self.event.options.all():
choices = [(choice.id, choice.value)
for choice in option.choices.all()]
if option.multi_choices:
initial = [] if option.id not in all_choices\
else all_choices[option.id]
field = forms.MultipleChoiceField(
label=option.name,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if option.id not in all_choices\
else all_choices[option.id][0]
field = forms.ChoiceField(label=option.name,
choices=choices,
widget=RadioSelect,
required=False,
initial=initial)
field.option_id = option.id
self.fields["option_%d" % option.id] = field
for commentfield in self.event.commentfields.all():
initial = commentfield.default
if registration is not None:
try:
initial = registration.comments \
.get(commentfield=commentfield).content
except EventCommentValue.DoesNotExist:
pass
widget = forms.Textarea if commentfield.fieldtype == "text" \
else forms.TextInput
field = forms.CharField(label=commentfield.name,
widget=widget,
required=False,
initial=initial)
field.comment_id = commentfield.id
self.fields["comment_%d" % commentfield.id] = field
def choices(self):
for name, value in self.cleaned_data.items():
if name.startswith('option_'):
yield (self.fields[name].option_id, value)
def comments(self):
for name, value in self.cleaned_data.items():
if name.startswith('comment_'):
yield (self.fields[name].comment_id, value)
class BaseEventRegistrationFormset(BaseFormSet):
def __init__(self, *args, **kwargs):
self.events = kwargs.pop('events')
self.current_registrations = kwargs.pop('current_registrations', None)
self.extra = len(self.events)
super(BaseEventRegistrationFormset, self).__init__(*args, **kwargs)
def _construct_form(self, index, **kwargs):
kwargs['event'] = self.events[index]
if self.current_registrations is not None:
kwargs['current_registration'] = self.current_registrations[index]
return super(BaseEventRegistrationFormset, self)._construct_form(
index, **kwargs)
EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
class CalendarForm(forms.ModelForm):
subscribe_to_events = forms.BooleanField(
initial=True,
label="Événements du COF")
subscribe_to_my_shows = forms.BooleanField(
initial=True,
label="Les spectacles pour lesquels j'ai obtenu une place")
other_shows = forms.ModelMultipleChoiceField(
label="Spectacles supplémentaires",
queryset=Spectacle.objects.filter(tirage__active=True),
widget=forms.CheckboxSelectMultiple,
required=False)
class Meta:
model = CalendarSubscription
fields = ['subscribe_to_events', 'subscribe_to_my_shows',
'other_shows']
class ClubsForm(forms.Form):
"""
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
"""
clubs = forms.ModelMultipleChoiceField(
label="Inscriptions aux clubs du COF",
queryset=Club.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False)

View file

@ -1,41 +0,0 @@
"""
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
"""
import os
import json
from django.core.management.base import BaseCommand
class MyBaseCommand(BaseCommand):
"""
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
"""
def from_json(self, filename, data_dir, klass,
callback=lambda obj: obj):
"""
Charge les objets contenus dans le fichier json référencé par
``filename`` dans la base de donnée. La fonction callback est appelées
sur chaque objet avant enregistrement.
"""
self.stdout.write("Chargement de {:s}".format(filename))
with open(os.path.join(data_dir, filename), 'r') as file:
descriptions = json.load(file)
objects = []
nb_new = 0
for description in descriptions:
qset = klass.objects.filter(**description)
try:
objects.append(qset.get())
except klass.DoesNotExist:
obj = klass(**description)
obj = callback(obj)
obj.save()
objects.append(obj)
nb_new += 1
self.stdout.write("- {:d} objets créés".format(nb_new))
self.stdout.write("- {:d} objets gardés en l'état"
.format(len(objects)-nb_new))
return objects

View file

@ -1,115 +0,0 @@
"""
Charge des données de test dans la BDD
- Utilisateurs
- Sondage
- Événement
- Petits cours
"""
import os
import random
from django.contrib.auth.models import User
from django.core.management import call_command
from gestioncof.management.base import MyBaseCommand
from gestioncof.petits_cours_models import (
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
PetitCoursAttributionCounter
)
# Où sont stockés les fichiers json
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
'data')
class Command(MyBaseCommand):
help = "Charge des données de test dans la BDD"
def add_arguments(self, parser):
"""
Permet de ne pas créer l'utilisateur "root".
"""
parser.add_argument(
'--no-root',
action='store_true',
dest='no-root',
default=False,
help='Ne crée pas l\'utilisateur "root"'
)
def handle(self, *args, **options):
# ---
# Utilisateurs
# ---
# Gaulois
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
for user in gaulois:
user.profile.is_cof = True
user.profile.save()
# Romains
self.from_json('romains.json', DATA_DIR, User)
# Root
no_root = options.get('no-root', False)
if not no_root:
self.stdout.write("Création de l'utilisateur root")
root, _ = User.objects.get_or_create(
username='root',
first_name='super',
last_name='user',
email='root@localhost')
root.set_password('root')
root.is_staff = True
root.is_superuser = True
root.profile.is_cof = True
root.profile.is_buro = True
root.profile.save()
root.save()
# ---
# Petits cours
# ---
self.stdout.write("Inscriptions au système des petits cours")
levels = [id for (id, verbose) in LEVELS_CHOICES]
subjects = list(PetitCoursSubject.objects.all())
nb_of_teachers = 0
for user in gaulois:
if random.randint(0, 1):
nb_of_teachers += 1
# L'utilisateur reçoit les demandes de petits cours
user.profile.petits_cours_accept = True
user.save()
# L'utilisateur est compétent dans une matière
subject = random.choice(subjects)
if not PetitCoursAbility.objects.filter(
user=user,
matiere=subject).exists():
PetitCoursAbility.objects.create(
user=user,
matiere=subject,
niveau=random.choice(levels),
agrege=bool(random.randint(0, 1))
)
# On initialise son compteur d'attributions
PetitCoursAttributionCounter.objects.get_or_create(
user=user,
matiere=subject
)
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
# ---
# Le BdA
# ---
call_command('loadbdadevdata')
# ---
# La K-Fêt
# ---
call_command('loadkfetdevdata')

View file

@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
"""
Import des mails de GestioCOF dans la base de donnée
"""
import json
import os
from custommail.models import Type, CustomMail, Variable
from django.core.management.base import BaseCommand
from django.contrib.contenttypes.models import ContentType
class Command(BaseCommand):
help = ("Va chercher les données mails de GestioCOF stocké au format json "
"dans /gestioncof/management/data/custommails.json. Le format des "
"données est celui donné par la commande :"
" `python manage.py dumpdata custommail --natural-foreign` "
"La bonne façon de mettre à jour ce fichier est donc de le "
"charger à l'aide de syncmails, le faire les modifications à "
"l'aide de l'interface administration et/ou du shell puis de le "
"remplacer par le nouveau résultat de la commande précédente.")
def handle(self, *args, **options):
path = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'data', 'custommail.json')
with open(path, 'r') as jsonfile:
mail_data = json.load(jsonfile)
# On se souvient à quel objet correspond quel pk du json
assoc = {'types': {}, 'mails': {}}
status = {'synced': 0, 'unchanged': 0}
for obj in mail_data:
fields = obj['fields']
# Pour les trois types d'objets :
# - On récupère les objets référencés par les clefs étrangères
# - On crée l'objet si nécessaire
# - On le stocke éventuellement dans les deux dictionnaires définis
# plus haut
# Variable types
if obj['model'] == 'custommail.variabletype':
fields['inner1'] = assoc['types'].get(fields['inner1'])
fields['inner2'] = assoc['types'].get(fields['inner2'])
if fields['kind'] == 'model':
fields['content_type'] = (
ContentType.objects
.get_by_natural_key(*fields['content_type'])
)
var_type, _ = Type.objects.get_or_create(**fields)
assoc['types'][obj['pk']] = var_type
# Custom mails
if obj['model'] == 'custommail.custommail':
mail = None
try:
mail = CustomMail.objects.get(
shortname=fields['shortname'])
status['unchanged'] += 1
except CustomMail.DoesNotExist:
mail = CustomMail.objects.create(**fields)
status['synced'] += 1
self.stdout.write(
'SYNCED {:s}'.format(fields['shortname']))
assoc['mails'][obj['pk']] = mail
# Variables
if obj['model'] == 'custommail.custommailvariable':
fields['custommail'] = assoc['mails'].get(fields['custommail'])
fields['type'] = assoc['types'].get(fields['type'])
try:
Variable.objects.get(
custommail=fields['custommail'],
name=fields['name']
)
except Variable.DoesNotExist:
Variable.objects.create(**fields)
# C'est agréable d'avoir le résultat affiché
self.stdout.write(
'{synced:d} mails synchronized {unchanged:d} unchanged'
.format(**status)
)

View file

@ -1,587 +0,0 @@
[
{
"model": "custommail.variabletype",
"pk": 1,
"fields": {
"content_type": [
"auth",
"user"
],
"inner1": null,
"kind": "model",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 2,
"fields": {
"content_type": null,
"inner1": null,
"kind": "int",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 3,
"fields": {
"content_type": [
"bda",
"spectacle"
],
"inner1": null,
"kind": "model",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 4,
"fields": {
"content_type": [
"bda",
"spectaclerevente"
],
"inner1": null,
"kind": "model",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 5,
"fields": {
"content_type": [
"sites",
"site"
],
"inner1": null,
"kind": "model",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 6,
"fields": {
"content_type": [
"gestioncof",
"petitcoursdemande"
],
"inner1": null,
"kind": "model",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 7,
"fields": {
"content_type": null,
"inner1": null,
"kind": "list",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 8,
"fields": {
"content_type": null,
"inner1": 1,
"kind": "list",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 9,
"fields": {
"content_type": null,
"inner1": null,
"kind": "pair",
"inner2": 8
}
},
{
"model": "custommail.variabletype",
"pk": 10,
"fields": {
"content_type": null,
"inner1": 9,
"kind": "list",
"inner2": null
}
},
{
"model": "custommail.variabletype",
"pk": 11,
"fields": {
"content_type": null,
"inner1": 3,
"kind": "list",
"inner2": null
}
},
{
"model": "custommail.custommail",
"pk": 1,
"fields": {
"shortname": "welcome",
"subject": "Bienvenue au COF",
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre",
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime."
}
},
{
"model": "custommail.custommail",
"pk": 2,
"fields": {
"shortname": "bda-rappel",
"subject": "{{ show }}",
"description": "Mail de rappel pour les spectacles BdA",
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts"
}
},
{
"model": "custommail.custommail",
"pk": 3,
"fields": {
"shortname": "bda-revente",
"subject": "{{ show }}",
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour le signaler qu'une place vient d'\u00eatre mise en vente.",
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-interested\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
"model": "custommail.custommail",
"pk": 4,
"fields": {
"shortname": "bda-shotgun",
"subject": "{{ show }}",
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-buy-revente\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
"model": "custommail.custommail",
"pk": 5,
"fields": {
"shortname": "bda-revente-winner",
"subject": "BdA-Revente : {{ show.title }}",
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente",
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
"model": "custommail.custommail",
"pk": 6,
"fields": {
"shortname": "bda-revente-loser",
"subject": "BdA-Revente : {{ show.title }}",
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente.",
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts"
}
},
{
"model": "custommail.custommail",
"pk": 7,
"fields": {
"shortname": "bda-revente-seller",
"subject": "BdA-Revente : {{ show.title }}",
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e",
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
"model": "custommail.custommail",
"pk": 8,
"fields": {
"shortname": "bda-revente-new",
"subject": "BdA-Revente : {{ show.title }}",
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires.",
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts"
}
},
{
"model": "custommail.custommail",
"pk": 9,
"fields": {
"shortname": "bda-buy-shotgun",
"subject": "BdA-Revente : {{ show.title }}",
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun.",
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})"
}
},
{
"model": "custommail.custommail",
"pk": 10,
"fields": {
"shortname": "petit-cours-mail-eleve",
"subject": "Petits cours ENS par le COF",
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours",
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours"
}
},
{
"model": "custommail.custommail",
"pk": 11,
"fields": {
"shortname": "petits-cours-mail-demandeur",
"subject": "Cours particuliers ENS",
"description": "Mail envoy\u00e9 aux personnent qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})",
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS"
}
},
{
"model": "custommail.custommail",
"pk": 12,
"fields": {
"shortname": "bda-attributions",
"subject": "R\u00e9sultats du tirage au sort",
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places",
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts"
}
},
{
"model": "custommail.custommail",
"pk": 13,
"fields": {
"shortname": "bda-attributions-decus",
"subject": "R\u00e9sultats du tirage au sort",
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place",
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts"
}
},
{
"model": "custommail.custommailvariable",
"pk": 1,
"fields": {
"name": "member",
"description": "Utilisateur de GestioCOF",
"custommail": 1,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 2,
"fields": {
"name": "member",
"description": "Utilisateur ayant eu une place pour ce spectacle",
"custommail": 2,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 3,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 2,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 4,
"fields": {
"name": "nb_attr",
"description": "Nombre de places obtenues",
"custommail": 2,
"type": 2
}
},
{
"model": "custommail.custommailvariable",
"pk": 5,
"fields": {
"name": "revente",
"description": "Revente mentionn\u00e9e dans le mail",
"custommail": 3,
"type": 4
}
},
{
"model": "custommail.custommailvariable",
"pk": 6,
"fields": {
"name": "member",
"description": "Personne int\u00e9ress\u00e9e par la place",
"custommail": 3,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 7,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 3,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 8,
"fields": {
"name": "site",
"description": "Site web (gestioCOF)",
"custommail": 3,
"type": 5
}
},
{
"model": "custommail.custommailvariable",
"pk": 9,
"fields": {
"name": "site",
"description": "Site web (gestioCOF)",
"custommail": 4,
"type": 5
}
},
{
"model": "custommail.custommailvariable",
"pk": 10,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 4,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 11,
"fields": {
"name": "member",
"description": "Personne int\u00e9ress\u00e9e par la place",
"custommail": 4,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 12,
"fields": {
"name": "acheteur",
"description": "Gagnant-e du tirage",
"custommail": 5,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 13,
"fields": {
"name": "vendeur",
"description": "Personne qui vend une place",
"custommail": 5,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 14,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 5,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 15,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 6,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 16,
"fields": {
"name": "vendeur",
"description": "Personne qui vend une place",
"custommail": 6,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 17,
"fields": {
"name": "acheteur",
"description": "Personne inscrite au tirage qui n'a pas eu la place",
"custommail": 6,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 18,
"fields": {
"name": "acheteur",
"description": "Gagnant-e du tirage",
"custommail": 7,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 19,
"fields": {
"name": "vendeur",
"description": "Personne qui vend une place",
"custommail": 7,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 20,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 7,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 21,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 8,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 22,
"fields": {
"name": "vendeur",
"description": "Personne qui vend la place",
"custommail": 8,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 23,
"fields": {
"name": "revente",
"description": "Revente mentionn\u00e9e dans le mail",
"custommail": 8,
"type": 4
}
},
{
"model": "custommail.custommailvariable",
"pk": 24,
"fields": {
"name": "vendeur",
"description": "Personne qui vend la place",
"custommail": 9,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 25,
"fields": {
"name": "show",
"description": "Spectacle",
"custommail": 9,
"type": 3
}
},
{
"model": "custommail.custommailvariable",
"pk": 26,
"fields": {
"name": "acheteur",
"description": "Personne qui prend la place au shotgun",
"custommail": 9,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 27,
"fields": {
"name": "demande",
"description": "Demande de petit cours",
"custommail": 10,
"type": 6
}
},
{
"model": "custommail.custommailvariable",
"pk": 28,
"fields": {
"name": "matieres",
"description": "Liste des mati\u00e8res concern\u00e9es par la demande",
"custommail": 10,
"type": 7
}
},
{
"model": "custommail.custommailvariable",
"pk": 29,
"fields": {
"name": "proposals",
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re",
"custommail": 11,
"type": 10
}
},
{
"model": "custommail.custommailvariable",
"pk": 30,
"fields": {
"name": "unsatisfied",
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer",
"custommail": 11,
"type": 7
}
},
{
"model": "custommail.custommailvariable",
"pk": 31,
"fields": {
"name": "places",
"description": "Places de spectacle du participant",
"custommail": 12,
"type": 11
}
},
{
"model": "custommail.custommailvariable",
"pk": 32,
"fields": {
"name": "member",
"description": "Participant du tirage au sort",
"custommail": 12,
"type": 1
}
},
{
"model": "custommail.custommailvariable",
"pk": 33,
"fields": {
"name": "member",
"description": "Participant du tirage au sort",
"custommail": 13,
"type": 1
}
}
]

View file

@ -1,368 +0,0 @@
[
{
"username": "Abraracourcix",
"email": "Abraracourcix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Abraracourcix"
},
{
"username": "Acidenitrix",
"email": "Acidenitrix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Acidenitrix"
},
{
"username": "Agecanonix",
"email": "Agecanonix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Agecanonix"
},
{
"username": "Alambix",
"email": "Alambix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Alambix"
},
{
"username": "Amerix",
"email": "Amerix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Amerix"
},
{
"username": "Amnesix",
"email": "Amnesix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Amnesix"
},
{
"username": "Aniline",
"email": "Aniline.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Aniline"
},
{
"username": "Aplusbegalix",
"email": "Aplusbegalix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Aplusbegalix"
},
{
"username": "Archeopterix",
"email": "Archeopterix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Archeopterix"
},
{
"username": "Assurancetourix",
"email": "Assurancetourix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Assurancetourix"
},
{
"username": "Asterix",
"email": "Asterix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Asterix"
},
{
"username": "Astronomix",
"email": "Astronomix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Astronomix"
},
{
"username": "Avoranfix",
"email": "Avoranfix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Avoranfix"
},
{
"username": "Barometrix",
"email": "Barometrix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Barometrix"
},
{
"username": "Beaufix",
"email": "Beaufix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Beaufix"
},
{
"username": "Berlix",
"email": "Berlix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Berlix"
},
{
"username": "Bonemine",
"email": "Bonemine.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Bonemine"
},
{
"username": "Boufiltre",
"email": "Boufiltre.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Boufiltre"
},
{
"username": "Catedralgotix",
"email": "Catedralgotix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Catedralgotix"
},
{
"username": "CesarLabeldecadix",
"email": "CesarLabeldecadix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "CesarLabeldecadix"
},
{
"username": "Cetautomatix",
"email": "Cetautomatix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Cetautomatix"
},
{
"username": "Cetyounix",
"email": "Cetyounix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Cetyounix"
},
{
"username": "Changeledix",
"email": "Changeledix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Changeledix"
},
{
"username": "Chanteclairix",
"email": "Chanteclairix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Chanteclairix"
},
{
"username": "Cicatrix",
"email": "Cicatrix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Cicatrix"
},
{
"username": "Comix",
"email": "Comix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Comix"
},
{
"username": "Diagnostix",
"email": "Diagnostix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Diagnostix"
},
{
"username": "Doublepolemix",
"email": "Doublepolemix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Doublepolemix"
},
{
"username": "Eponine",
"email": "Eponine.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Eponine"
},
{
"username": "Falbala",
"email": "Falbala.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Falbala"
},
{
"username": "Fanzine",
"email": "Fanzine.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Fanzine"
},
{
"username": "Gelatine",
"email": "Gelatine.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Gelatine"
},
{
"username": "Goudurix",
"email": "Goudurix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Goudurix"
},
{
"username": "Homeopatix",
"email": "Homeopatix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Homeopatix"
},
{
"username": "Idefix",
"email": "Idefix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Idefix"
},
{
"username": "Ielosubmarine",
"email": "Ielosubmarine.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Ielosubmarine"
},
{
"username": "Keskonrix",
"email": "Keskonrix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Keskonrix"
},
{
"username": "Lentix",
"email": "Lentix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Lentix"
},
{
"username": "Maestria",
"email": "Maestria.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Maestria"
},
{
"username": "MaitrePanix",
"email": "MaitrePanix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "MaitrePanix"
},
{
"username": "MmeAgecanonix",
"email": "MmeAgecanonix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "MmeAgecanonix"
},
{
"username": "Moralelastix",
"email": "Moralelastix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Moralelastix"
},
{
"username": "Obelix",
"email": "Obelix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Obelix"
},
{
"username": "Obelodalix",
"email": "Obelodalix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Obelodalix"
},
{
"username": "Odalix",
"email": "Odalix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Odalix"
},
{
"username": "Ordralfabetix",
"email": "Ordralfabetix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Ordralfabetix"
},
{
"username": "Orthopedix",
"email": "Orthopedix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Orthopedix"
},
{
"username": "Panoramix",
"email": "Panoramix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Panoramix"
},
{
"username": "Plaintcontrix",
"email": "Plaintcontrix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Plaintcontrix"
},
{
"username": "Praline",
"email": "Praline.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Praline"
},
{
"username": "Prefix",
"email": "Prefix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Prefix"
},
{
"username": "Prolix",
"email": "Prolix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Prolix"
},
{
"username": "Pronostix",
"email": "Pronostix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Pronostix"
},
{
"username": "Quatredeusix",
"email": "Quatredeusix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Quatredeusix"
},
{
"username": "Saingesix",
"email": "Saingesix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Saingesix"
},
{
"username": "Segregationnix",
"email": "Segregationnix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Segregationnix"
},
{
"username": "Septantesix",
"email": "Septantesix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Septantesix"
},
{
"username": "Tournedix",
"email": "Tournedix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Tournedix"
},
{
"username": "Tragicomix",
"email": "Tragicomix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Tragicomix"
},
{
"username": "Coriza",
"email": "Coriza.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Coriza"
},
{
"username": "Zerozerosix",
"email": "Zerozerosix.gaulois@ens.fr",
"last_name": "Gaulois",
"first_name": "Zerozerosix"
}
]

View file

@ -1,614 +0,0 @@
[
{
"username": "Abel",
"email": "Abel.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Abel"
},
{
"username": "Abelardus",
"email": "Abelardus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Abelardus"
},
{
"username": "Abrahamus",
"email": "Abrahamus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Abrahamus"
},
{
"username": "Acacius",
"email": "Acacius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Acacius"
},
{
"username": "Accius",
"email": "Accius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Accius"
},
{
"username": "Achaicus",
"email": "Achaicus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Achaicus"
},
{
"username": "Achill",
"email": "Achill.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Achill"
},
{
"username": "Achilles",
"email": "Achilles.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Achilles"
},
{
"username": "Achilleus",
"email": "Achilleus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Achilleus"
},
{
"username": "Acrisius",
"email": "Acrisius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Acrisius"
},
{
"username": "Actaeon",
"email": "Actaeon.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Actaeon"
},
{
"username": "Acteon",
"email": "Acteon.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Acteon"
},
{
"username": "Adalricus",
"email": "Adalricus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adalricus"
},
{
"username": "Adelfonsus",
"email": "Adelfonsus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adelfonsus"
},
{
"username": "Adelphus",
"email": "Adelphus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adelphus"
},
{
"username": "Adeodatus",
"email": "Adeodatus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adeodatus"
},
{
"username": "Adolfus",
"email": "Adolfus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adolfus"
},
{
"username": "Adolphus",
"email": "Adolphus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adolphus"
},
{
"username": "Adrastus",
"email": "Adrastus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adrastus"
},
{
"username": "Adrianus",
"email": "Adrianus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Adrianus"
},
{
"username": "\u00c6gidius",
"email": "\u00c6gidius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6gidius"
},
{
"username": "\u00c6lia",
"email": "\u00c6lia.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6lia"
},
{
"username": "\u00c6lianus",
"email": "\u00c6lianus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6lianus"
},
{
"username": "\u00c6milianus",
"email": "\u00c6milianus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6milianus"
},
{
"username": "\u00c6milius",
"email": "\u00c6milius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6milius"
},
{
"username": "Aeneas",
"email": "Aeneas.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aeneas"
},
{
"username": "\u00c6olus",
"email": "\u00c6olus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6olus"
},
{
"username": "\u00c6schylus",
"email": "\u00c6schylus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6schylus"
},
{
"username": "\u00c6son",
"email": "\u00c6son.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6son"
},
{
"username": "\u00c6sop",
"email": "\u00c6sop.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6sop"
},
{
"username": "\u00c6ther",
"email": "\u00c6ther.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6ther"
},
{
"username": "\u00c6tius",
"email": "\u00c6tius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "\u00c6tius"
},
{
"username": "Agapetus",
"email": "Agapetus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Agapetus"
},
{
"username": "Agapitus",
"email": "Agapitus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Agapitus"
},
{
"username": "Agapius",
"email": "Agapius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Agapius"
},
{
"username": "Agathangelus",
"email": "Agathangelus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Agathangelus"
},
{
"username": "Aigidius",
"email": "Aigidius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aigidius"
},
{
"username": "Aiolus",
"email": "Aiolus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aiolus"
},
{
"username": "Ajax",
"email": "Ajax.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Ajax"
},
{
"username": "Alair",
"email": "Alair.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alair"
},
{
"username": "Alaricus",
"email": "Alaricus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alaricus"
},
{
"username": "Albanus",
"email": "Albanus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Albanus"
},
{
"username": "Alberic",
"email": "Alberic.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alberic"
},
{
"username": "Albericus",
"email": "Albericus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Albericus"
},
{
"username": "Albertus",
"email": "Albertus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Albertus"
},
{
"username": "Albinus",
"email": "Albinus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Albinus"
},
{
"username": "Albus",
"email": "Albus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Albus"
},
{
"username": "Alcaeus",
"email": "Alcaeus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alcaeus"
},
{
"username": "Alcander",
"email": "Alcander.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alcander"
},
{
"username": "Alcimus",
"email": "Alcimus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alcimus"
},
{
"username": "Alcinder",
"email": "Alcinder.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alcinder"
},
{
"username": "Alerio",
"email": "Alerio.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alerio"
},
{
"username": "Alexandrus",
"email": "Alexandrus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alexandrus"
},
{
"username": "Alexis",
"email": "Alexis.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alexis"
},
{
"username": "Alexius",
"email": "Alexius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alexius"
},
{
"username": "Alexus",
"email": "Alexus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alexus"
},
{
"username": "Alfonsus",
"email": "Alfonsus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alfonsus"
},
{
"username": "Alfredus",
"email": "Alfredus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alfredus"
},
{
"username": "Almericus",
"email": "Almericus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Almericus"
},
{
"username": "Aloisius",
"email": "Aloisius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aloisius"
},
{
"username": "Aloysius",
"email": "Aloysius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aloysius"
},
{
"username": "Alphaeus",
"email": "Alphaeus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alphaeus"
},
{
"username": "Alpheaus",
"email": "Alpheaus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alpheaus"
},
{
"username": "Alpheus",
"email": "Alpheus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alpheus"
},
{
"username": "Alphoeus",
"email": "Alphoeus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alphoeus"
},
{
"username": "Alphonsus",
"email": "Alphonsus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alphonsus"
},
{
"username": "Alphonzus",
"email": "Alphonzus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alphonzus"
},
{
"username": "Alvinius",
"email": "Alvinius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alvinius"
},
{
"username": "Alvredus",
"email": "Alvredus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Alvredus"
},
{
"username": "Amadeus",
"email": "Amadeus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amadeus"
},
{
"username": "Amaliricus",
"email": "Amaliricus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amaliricus"
},
{
"username": "Amandus",
"email": "Amandus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amandus"
},
{
"username": "Amantius",
"email": "Amantius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amantius"
},
{
"username": "Amarandus",
"email": "Amarandus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amarandus"
},
{
"username": "Amaranthus",
"email": "Amaranthus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amaranthus"
},
{
"username": "Amatus",
"email": "Amatus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amatus"
},
{
"username": "Ambrosianus",
"email": "Ambrosianus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Ambrosianus"
},
{
"username": "Ambrosius",
"email": "Ambrosius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Ambrosius"
},
{
"username": "Amedeus",
"email": "Amedeus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amedeus"
},
{
"username": "Americus",
"email": "Americus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Americus"
},
{
"username": "Amlethus",
"email": "Amlethus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amlethus"
},
{
"username": "Amletus",
"email": "Amletus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amletus"
},
{
"username": "Amor",
"email": "Amor.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amor"
},
{
"username": "Ampelius",
"email": "Ampelius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Ampelius"
},
{
"username": "Amphion",
"email": "Amphion.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Amphion"
},
{
"username": "Anacletus",
"email": "Anacletus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anacletus"
},
{
"username": "Anastasius",
"email": "Anastasius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anastasius"
},
{
"username": "Anastatius",
"email": "Anastatius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anastatius"
},
{
"username": "Anastius",
"email": "Anastius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anastius"
},
{
"username": "Anatolius",
"email": "Anatolius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anatolius"
},
{
"username": "Androcles",
"email": "Androcles.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Androcles"
},
{
"username": "Andronicus",
"email": "Andronicus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Andronicus"
},
{
"username": "Anencletus",
"email": "Anencletus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anencletus"
},
{
"username": "Angelicus",
"email": "Angelicus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Angelicus"
},
{
"username": "Angelus",
"email": "Angelus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Angelus"
},
{
"username": "Anicetus",
"email": "Anicetus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Anicetus"
},
{
"username": "Antigonus",
"email": "Antigonus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Antigonus"
},
{
"username": "Antipater",
"email": "Antipater.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Antipater"
},
{
"username": "Antoninus",
"email": "Antoninus.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Antoninus"
},
{
"username": "Antonius",
"email": "Antonius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Antonius"
},
{
"username": "Aphrodisius",
"email": "Aphrodisius.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Aphrodisius"
},
{
"username": "Apollinaris",
"email": "Apollinaris.Romain@ens.fr",
"last_name": "Romain",
"first_name": "Apollinaris"
}
]

View file

@ -1,333 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Clipper',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('username', models.CharField(max_length=20, verbose_name=b'Identifiant')),
('fullname', models.CharField(max_length=200, verbose_name=b'Nom complet')),
],
),
migrations.CreateModel(
name='Club',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name=b'Nom')),
('description', models.TextField(verbose_name=b'Description')),
('membres', models.ManyToManyField(related_name='clubs', to=settings.AUTH_USER_MODEL)),
('respos', models.ManyToManyField(related_name='clubs_geres', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='CofProfile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('login_clipper', models.CharField(max_length=8, verbose_name=b'Login clipper', blank=True)),
('is_cof', models.BooleanField(default=False, verbose_name=b'Membre du COF')),
('num', models.IntegerField(default=0, verbose_name=b"Num\xc3\xa9ro d'adh\xc3\xa9rent", blank=True)),
('phone', models.CharField(max_length=20, verbose_name=b'T\xc3\xa9l\xc3\xa9phone', blank=True)),
('occupation', models.CharField(default=b'1A', max_length=9, verbose_name='Occupation', choices=[(b'exterieur', 'Ext\xe9rieur'), (b'1A', '1A'), (b'2A', '2A'), (b'3A', '3A'), (b'4A', '4A'), (b'archicube', 'Archicube'), (b'doctorant', 'Doctorant'), (b'CST', 'CST')])),
('departement', models.CharField(max_length=50, verbose_name='D\xe9partement', blank=True)),
('type_cotiz', models.CharField(default=b'normalien', max_length=9, verbose_name='Type de cotisation', choices=[(b'etudiant', 'Normalien \xe9tudiant'), (b'normalien', 'Normalien \xe9l\xe8ve'), (b'exterieur', 'Ext\xe9rieur')])),
('mailing_cof', models.BooleanField(default=False, verbose_name=b'Recevoir les mails COF')),
('mailing_bda', models.BooleanField(default=False, verbose_name=b'Recevoir les mails BdA')),
('mailing_bda_revente', models.BooleanField(default=False, verbose_name=b'Recevoir les mails de revente de places BdA')),
('comments', models.TextField(verbose_name=b'Commentaires visibles uniquement par le Buro', blank=True)),
('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')),
('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')),
('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)),
('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Profil COF',
'verbose_name_plural': 'Profils COF',
},
),
migrations.CreateModel(
name='CustomMail',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('shortname', models.SlugField()),
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
('content', models.TextField(verbose_name=b'Contenu')),
('comments', models.TextField(verbose_name=b'Informations contextuelles sur le mail', blank=True)),
],
options={
'verbose_name': 'Mails personnalisables',
},
),
migrations.CreateModel(
name='Event',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
('location', models.CharField(max_length=200, verbose_name=b'Lieu')),
('start_date', models.DateField(null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True)),
('end_date', models.DateField(null=True, verbose_name=b'Date de fin', blank=True)),
('description', models.TextField(verbose_name=b'Description', blank=True)),
('registration_open', models.BooleanField(default=True, verbose_name=b'Inscriptions ouvertes')),
('old', models.BooleanField(default=False, verbose_name=b'Archiver (\xc3\xa9v\xc3\xa9nement fini)')),
],
options={
'verbose_name': '\xc9v\xe9nement',
},
),
migrations.CreateModel(
name='EventCommentField',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name=b'Champ')),
('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])),
('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)),
('event', models.ForeignKey(related_name='commentfields', to='gestioncof.Event')),
],
options={
'verbose_name': 'Champ',
},
),
migrations.CreateModel(
name='EventCommentValue',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)),
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField')),
],
),
migrations.CreateModel(
name='EventOption',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name=b'Option')),
('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
('event', models.ForeignKey(related_name='options', to='gestioncof.Event')),
],
options={
'verbose_name': 'Option',
},
),
migrations.CreateModel(
name='EventOptionChoice',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('value', models.CharField(max_length=200, verbose_name=b'Valeur')),
('event_option', models.ForeignKey(related_name='choices', to='gestioncof.EventOption')),
],
options={
'verbose_name': 'Choix',
},
),
migrations.CreateModel(
name='EventRegistration',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')),
('event', models.ForeignKey(to='gestioncof.Event')),
('filledcomments', models.ManyToManyField(to='gestioncof.EventCommentField', through='gestioncof.EventCommentValue')),
('options', models.ManyToManyField(to='gestioncof.EventOptionChoice')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Inscription',
},
),
migrations.CreateModel(
name='PetitCoursAbility',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('niveau', models.CharField(max_length=12, verbose_name='Niveau', choices=[(b'college', 'Coll\xe8ge'), (b'lycee', 'Lyc\xe9e'), (b'prepa1styear', 'Pr\xe9pa 1\xe8re ann\xe9e / L1'), (b'prepa2ndyear', 'Pr\xe9pa 2\xe8me ann\xe9e / L2'), (b'licence3', 'Licence 3'), (b'other', 'Autre (pr\xe9ciser dans les commentaires)')])),
('agrege', models.BooleanField(default=False, verbose_name='Agr\xe9g\xe9')),
],
options={
'verbose_name': 'Comp\xe9tence petits cours',
'verbose_name_plural': 'Comp\xe9tences des petits cours',
},
),
migrations.CreateModel(
name='PetitCoursAttribution',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('date', models.DateTimeField(auto_now_add=True, verbose_name="Date d'attribution")),
('rank', models.IntegerField(verbose_name=b"Rang dans l'email")),
('selected', models.BooleanField(default=False, verbose_name='S\xe9lectionn\xe9 par le demandeur')),
],
options={
'verbose_name': 'Attribution de petits cours',
'verbose_name_plural': 'Attributions de petits cours',
},
),
migrations.CreateModel(
name='PetitCoursAttributionCounter',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('count', models.IntegerField(default=0, verbose_name=b"Nombre d'envois")),
],
options={
'verbose_name': "Compteur d'attribution de petits cours",
'verbose_name_plural': "Compteurs d'attributions de petits cours",
},
),
migrations.CreateModel(
name='PetitCoursDemande',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name='Nom/pr\xe9nom')),
('email', models.CharField(max_length=300, verbose_name='Adresse email')),
('phone', models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone (facultatif)', blank=True)),
('quand', models.CharField(help_text='Indiquez ici la p\xe9riode d\xe9sir\xe9e pour les petits cours (vacances scolaires, semaine, week-end).', max_length=300, verbose_name='Quand ?', blank=True)),
('freq', models.CharField(help_text='Indiquez ici la fr\xe9quence envisag\xe9e (hebdomadaire, 2 fois par semaine, ...)', max_length=300, verbose_name='Fr\xe9quence', blank=True)),
('lieu', models.CharField(help_text='Si vous avez avez une pr\xe9f\xe9rence sur le lieu.', max_length=300, verbose_name='Lieu (si pr\xe9f\xe9rence)', blank=True)),
('agrege_requis', models.BooleanField(default=False, verbose_name='Agr\xe9g\xe9 requis')),
('niveau', models.CharField(default=b'', max_length=12, verbose_name='Niveau', choices=[(b'college', 'Coll\xe8ge'), (b'lycee', 'Lyc\xe9e'), (b'prepa1styear', 'Pr\xe9pa 1\xe8re ann\xe9e / L1'), (b'prepa2ndyear', 'Pr\xe9pa 2\xe8me ann\xe9e / L2'), (b'licence3', 'Licence 3'), (b'other', 'Autre (pr\xe9ciser dans les commentaires)')])),
('remarques', models.TextField(verbose_name='Remarques et pr\xe9cisions', blank=True)),
('traitee', models.BooleanField(default=False, verbose_name='Trait\xe9e')),
('processed', models.DateTimeField(verbose_name='Date de traitement', blank=True)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Date de cr\xe9ation')),
],
options={
'verbose_name': 'Demande de petits cours',
'verbose_name_plural': 'Demandes de petits cours',
},
),
migrations.CreateModel(
name='PetitCoursSubject',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=30, verbose_name='Mati\xe8re')),
('users', models.ManyToManyField(related_name='petits_cours_matieres', through='gestioncof.PetitCoursAbility', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Mati\xe8re de petits cours',
'verbose_name_plural': 'Mati\xe8res des petits cours',
},
),
migrations.CreateModel(
name='Survey',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
('details', models.TextField(verbose_name=b'D\xc3\xa9tails', blank=True)),
('survey_open', models.BooleanField(default=True, verbose_name=b'Sondage ouvert')),
('old', models.BooleanField(default=False, verbose_name=b'Archiver (sondage fini)')),
],
options={
'verbose_name': 'Sondage',
},
),
migrations.CreateModel(
name='SurveyAnswer',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
'verbose_name': 'R\xe9ponses',
},
),
migrations.CreateModel(
name='SurveyQuestion',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('question', models.CharField(max_length=200, verbose_name=b'Question')),
('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
('survey', models.ForeignKey(related_name='questions', to='gestioncof.Survey')),
],
options={
'verbose_name': 'Question',
},
),
migrations.CreateModel(
name='SurveyQuestionAnswer',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')),
('survey_question', models.ForeignKey(related_name='answers', to='gestioncof.SurveyQuestion')),
],
options={
'verbose_name': 'R\xe9ponse',
},
),
migrations.AddField(
model_name='surveyanswer',
name='answers',
field=models.ManyToManyField(related_name='selected_by', to='gestioncof.SurveyQuestionAnswer'),
),
migrations.AddField(
model_name='surveyanswer',
name='survey',
field=models.ForeignKey(to='gestioncof.Survey'),
),
migrations.AddField(
model_name='surveyanswer',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='petitcoursdemande',
name='matieres',
field=models.ManyToManyField(related_name='demandes', verbose_name='Mati\xe8res', to='gestioncof.PetitCoursSubject'),
),
migrations.AddField(
model_name='petitcoursdemande',
name='traitee_par',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True),
),
migrations.AddField(
model_name='petitcoursattributioncounter',
name='matiere',
field=models.ForeignKey(verbose_name='Matiere', to='gestioncof.PetitCoursSubject'),
),
migrations.AddField(
model_name='petitcoursattributioncounter',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='petitcoursattribution',
name='demande',
field=models.ForeignKey(verbose_name='Demande', to='gestioncof.PetitCoursDemande'),
),
migrations.AddField(
model_name='petitcoursattribution',
name='matiere',
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'),
),
migrations.AddField(
model_name='petitcoursattribution',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='petitcoursability',
name='matiere',
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'),
),
migrations.AddField(
model_name='petitcoursability',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='eventcommentvalue',
name='registration',
field=models.ForeignKey(related_name='comments', to='gestioncof.EventRegistration'),
),
migrations.AlterUniqueTogether(
name='surveyanswer',
unique_together=set([('user', 'survey')]),
),
migrations.AlterUniqueTogether(
name='eventregistration',
unique_together=set([('user', 'event')]),
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='petitcoursdemande',
name='processed',
field=models.DateTimeField(null=True, verbose_name='Date de traitement', blank=True),
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0002_enable_unprocessed_demandes'),
]
operations = [
migrations.AddField(
model_name='event',
name='image',
field=models.ImageField(upload_to=b'imgs/events/', null=True, verbose_name=b'Image', blank=True),
),
]

View file

@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def create_mail(apps, schema_editor):
CustomMail = apps.get_model("gestioncof", "CustomMail")
db_alias = schema_editor.connection.alias
if CustomMail.objects.filter(shortname="bienvenue").count() == 0:
CustomMail.objects.using(db_alias).bulk_create([
CustomMail(
shortname="bienvenue",
title="Bienvenue au COF",
content="Mail de bienvenue au COF, envoyé automatiquement à " \
+ "l'inscription.\n\n" \
+ "Les balises {{ ... }} sont interprétées comme expliqué " \
+ "ci-dessous à l'envoi.",
comments="{{ nom }} \t fullname de la personne.\n"\
+ "{{ prenom }} \t prénom de la personne.")
])
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0003_event_image'),
]
operations = [
# Pas besoin de supprimer le mail lors de la migration dans l'autre
# sens.
migrations.RunPython(create_mail, migrations.RunPython.noop),
]

View file

@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0004_registration_mail'),
]
operations = [
migrations.AlterModelOptions(
name='custommail',
options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
),
migrations.AlterModelOptions(
name='eventoptionchoice',
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
),
migrations.AlterField(
model_name='cofprofile',
name='is_buro',
field=models.BooleanField(default=False, verbose_name='Membre du Bur\xf4'),
),
migrations.AlterField(
model_name='cofprofile',
name='num',
field=models.IntegerField(default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True),
),
migrations.AlterField(
model_name='cofprofile',
name='phone',
field=models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone', blank=True),
),
migrations.AlterField(
model_name='event',
name='old',
field=models.BooleanField(default=False, verbose_name='Archiver (\xe9v\xe9nement fini)'),
),
migrations.AlterField(
model_name='event',
name='start_date',
field=models.DateField(null=True, verbose_name='Date de d\xe9but', blank=True),
),
migrations.AlterField(
model_name='eventcommentfield',
name='default',
field=models.TextField(verbose_name='Valeur par d\xe9faut', blank=True),
),
migrations.AlterField(
model_name='eventregistration',
name='paid',
field=models.BooleanField(default=False, verbose_name='A pay\xe9'),
),
migrations.AlterField(
model_name='survey',
name='details',
field=models.TextField(verbose_name='D\xe9tails', blank=True),
),
migrations.AlterField(
model_name='surveyquestionanswer',
name='answer',
field=models.CharField(max_length=200, verbose_name='R\xe9ponse'),
),
]

View file

@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('bda', '0004_mails-rappel'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('gestioncof', '0005_encoding'),
]
operations = [
migrations.CreateModel(
name='CalendarSubscription',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('token', models.UUIDField()),
('subscribe_to_events', models.BooleanField(default=True)),
('subscribe_to_my_shows', models.BooleanField(default=True)),
('other_shows', models.ManyToManyField(to='bda.Spectacle')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterModelOptions(
name='custommail',
options={'verbose_name': 'Mail personnalisable',
'verbose_name_plural': 'Mails personnalisables'},
),
migrations.AlterModelOptions(
name='eventoptionchoice',
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
),
migrations.AlterField(
model_name='event',
name='end_date',
field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
blank=True),
),
migrations.AlterField(
model_name='event',
name='start_date',
field=models.DateTimeField(
null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True),
),
]

View file

@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0006_add_calendar'),
]
operations = [
migrations.AlterField(
model_name='club',
name='name',
field=models.CharField(unique=True, max_length=200,
verbose_name='Nom')
),
migrations.AlterField(
model_name='club',
name='description',
field=models.TextField(verbose_name='Description', blank=True)
),
migrations.AlterField(
model_name='club',
name='membres',
field=models.ManyToManyField(related_name='clubs',
to=settings.AUTH_USER_MODEL,
blank=True),
),
migrations.AlterField(
model_name='club',
name='respos',
field=models.ManyToManyField(related_name='clubs_geres',
to=settings.AUTH_USER_MODEL,
blank=True),
),
migrations.AlterField(
model_name='event',
name='start_date',
field=models.DateTimeField(null=True,
verbose_name='Date de d\xe9but',
blank=True),
),
]

View file

@ -1,253 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def forwards(apps, schema_editor):
Profile = apps.get_model("gestioncof", "CofProfile")
Profile.objects.update(comments="")
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0007_alter_club'),
]
operations = [
migrations.AlterField(
model_name='clipper',
name='fullname',
field=models.CharField(verbose_name='Nom complet', max_length=200),
),
migrations.AlterField(
model_name='clipper',
name='username',
field=models.CharField(verbose_name='Identifiant', max_length=20),
),
migrations.AlterField(
model_name='cofprofile',
name='comments',
field=models.TextField(
verbose_name="Commentaires visibles par l'utilisateur",
blank=True),
),
migrations.AlterField(
model_name='cofprofile',
name='is_cof',
field=models.BooleanField(verbose_name='Membre du COF',
default=False),
),
migrations.AlterField(
model_name='cofprofile',
name='login_clipper',
field=models.CharField(verbose_name='Login clipper', max_length=8,
blank=True),
),
migrations.AlterField(
model_name='cofprofile',
name='mailing_bda',
field=models.BooleanField(verbose_name='Recevoir les mails BdA',
default=False),
),
migrations.AlterField(
model_name='cofprofile',
name='mailing_bda_revente',
field=models.BooleanField(
verbose_name='Recevoir les mails de revente de places BdA',
default=False),
),
migrations.AlterField(
model_name='cofprofile',
name='mailing_cof',
field=models.BooleanField(verbose_name='Recevoir les mails COF',
default=False),
),
migrations.AlterField(
model_name='cofprofile',
name='occupation',
field=models.CharField(verbose_name='Occupation',
choices=[('exterieur', 'Extérieur'),
('1A', '1A'),
('2A', '2A'),
('3A', '3A'),
('4A', '4A'),
('archicube', 'Archicube'),
('doctorant', 'Doctorant'),
('CST', 'CST')],
max_length=9, default='1A'),
),
migrations.AlterField(
model_name='cofprofile',
name='petits_cours_accept',
field=models.BooleanField(verbose_name='Recevoir des petits cours',
default=False),
),
migrations.AlterField(
model_name='cofprofile',
name='petits_cours_remarques',
field=models.TextField(
blank=True,
verbose_name='Remarques et précisions pour les petits cours',
default=''),
),
migrations.AlterField(
model_name='cofprofile',
name='type_cotiz',
field=models.CharField(
verbose_name='Type de cotisation',
choices=[('etudiant', 'Normalien étudiant'),
('normalien', 'Normalien élève'),
('exterieur', 'Extérieur')],
max_length=9, default='normalien'),
),
migrations.AlterField(
model_name='custommail',
name='comments',
field=models.TextField(
verbose_name='Informations contextuelles sur le mail',
blank=True),
),
migrations.AlterField(
model_name='custommail',
name='content',
field=models.TextField(verbose_name='Contenu'),
),
migrations.AlterField(
model_name='custommail',
name='title',
field=models.CharField(verbose_name='Titre', max_length=200),
),
migrations.AlterField(
model_name='event',
name='description',
field=models.TextField(verbose_name='Description', blank=True),
),
migrations.AlterField(
model_name='event',
name='end_date',
field=models.DateTimeField(null=True, verbose_name='Date de fin',
blank=True),
),
migrations.AlterField(
model_name='event',
name='image',
field=models.ImageField(upload_to='imgs/events/', null=True,
verbose_name='Image', blank=True),
),
migrations.AlterField(
model_name='event',
name='location',
field=models.CharField(verbose_name='Lieu', max_length=200),
),
migrations.AlterField(
model_name='event',
name='registration_open',
field=models.BooleanField(verbose_name='Inscriptions ouvertes',
default=True),
),
migrations.AlterField(
model_name='event',
name='title',
field=models.CharField(verbose_name='Titre', max_length=200),
),
migrations.AlterField(
model_name='eventcommentfield',
name='fieldtype',
field=models.CharField(verbose_name='Type',
choices=[('text', 'Texte long'),
('char', 'Texte court')],
max_length=10, default='text'),
),
migrations.AlterField(
model_name='eventcommentfield',
name='name',
field=models.CharField(verbose_name='Champ', max_length=200),
),
migrations.AlterField(
model_name='eventcommentvalue',
name='content',
field=models.TextField(null=True, verbose_name='Contenu',
blank=True),
),
migrations.AlterField(
model_name='eventoption',
name='multi_choices',
field=models.BooleanField(verbose_name='Choix multiples',
default=False),
),
migrations.AlterField(
model_name='eventoption',
name='name',
field=models.CharField(verbose_name='Option', max_length=200),
),
migrations.AlterField(
model_name='eventoptionchoice',
name='value',
field=models.CharField(verbose_name='Valeur', max_length=200),
),
migrations.AlterField(
model_name='petitcoursability',
name='niveau',
field=models.CharField(
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
('prepa1styear', 'Prépa 1ère année / L1'),
('prepa2ndyear', 'Prépa 2ème année / L2'),
('licence3', 'Licence 3'),
('other', 'Autre (préciser dans les commentaires)')],
max_length=12, verbose_name='Niveau'),
),
migrations.AlterField(
model_name='petitcoursattribution',
name='rank',
field=models.IntegerField(verbose_name="Rang dans l'email"),
),
migrations.AlterField(
model_name='petitcoursattributioncounter',
name='count',
field=models.IntegerField(verbose_name="Nombre d'envois",
default=0),
),
migrations.AlterField(
model_name='petitcoursdemande',
name='niveau',
field=models.CharField(
verbose_name='Niveau',
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
('prepa1styear', 'Prépa 1ère année / L1'),
('prepa2ndyear', 'Prépa 2ème année / L2'),
('licence3', 'Licence 3'),
('other', 'Autre (préciser dans les commentaires)')],
max_length=12, default=''),
),
migrations.AlterField(
model_name='survey',
name='old',
field=models.BooleanField(verbose_name='Archiver (sondage fini)',
default=False),
),
migrations.AlterField(
model_name='survey',
name='survey_open',
field=models.BooleanField(verbose_name='Sondage ouvert',
default=True),
),
migrations.AlterField(
model_name='survey',
name='title',
field=models.CharField(verbose_name='Titre', max_length=200),
),
migrations.AlterField(
model_name='surveyquestion',
name='multi_answers',
field=models.BooleanField(verbose_name='Choix multiples',
default=False),
),
migrations.AlterField(
model_name='surveyquestion',
name='question',
field=models.CharField(verbose_name='Question', max_length=200),
),
migrations.RunPython(forwards, migrations.RunPython.noop),
]

View file

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0009_delete_clipper'),
]
operations = [
migrations.DeleteModel(
name='CustomMail',
),
]

View file

@ -1,256 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
import django.utils.six as six
from django.db.models.signals import post_save, post_delete
from gestioncof.petits_cours_models import choices_length
from bda.models import Spectacle
OCCUPATION_CHOICES = (
('exterieur', _("Extérieur")),
('1A', _("1A")),
('2A', _("2A")),
('3A', _("3A")),
('4A', _("4A")),
('archicube', _("Archicube")),
('doctorant', _("Doctorant")),
('CST', _("CST")),
)
TYPE_COTIZ_CHOICES = (
('etudiant', _("Normalien étudiant")),
('normalien', _("Normalien élève")),
('exterieur', _("Extérieur")),
)
TYPE_COMMENT_FIELD = (
('text', _("Texte long")),
('char', _("Texte court")),
)
@python_2_unicode_compatible
class CofProfile(models.Model):
user = models.OneToOneField(User, related_name="profile")
login_clipper = models.CharField("Login clipper", max_length=8, blank=True)
is_cof = models.BooleanField("Membre du COF", default=False)
num = models.IntegerField("Numéro d'adhérent", blank=True, default=0)
phone = models.CharField("Téléphone", max_length=20, blank=True)
occupation = models.CharField(_("Occupation"),
default="1A",
choices=OCCUPATION_CHOICES,
max_length=choices_length(
OCCUPATION_CHOICES))
departement = models.CharField(_("Département"), max_length=50,
blank=True)
type_cotiz = models.CharField(_("Type de cotisation"),
default="normalien",
choices=TYPE_COTIZ_CHOICES,
max_length=choices_length(
TYPE_COTIZ_CHOICES))
mailing_cof = models.BooleanField("Recevoir les mails COF", default=False)
mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False)
mailing_bda_revente = models.BooleanField(
"Recevoir les mails de revente de places BdA", default=False)
comments = models.TextField(
"Commentaires visibles par l'utilisateur", blank=True)
is_buro = models.BooleanField("Membre du Burô", default=False)
petits_cours_accept = models.BooleanField(
"Recevoir des petits cours", default=False)
petits_cours_remarques = models.TextField(
_("Remarques et précisions pour les petits cours"),
blank=True, default="")
class Meta:
verbose_name = "Profil COF"
verbose_name_plural = "Profils COF"
def __str__(self):
return six.text_type(self.user.username)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
CofProfile.objects.get_or_create(user=instance)
@receiver(post_delete, sender=CofProfile)
def post_delete_user(sender, instance, *args, **kwargs):
instance.user.delete()
@python_2_unicode_compatible
class Club(models.Model):
name = models.CharField("Nom", max_length=200, unique=True)
description = models.TextField("Description", blank=True)
respos = models.ManyToManyField(User, related_name="clubs_geres",
blank=True)
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
def __str__(self):
return self.name
@python_2_unicode_compatible
class Event(models.Model):
title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200)
start_date = models.DateTimeField("Date de début", blank=True, null=True)
end_date = models.DateTimeField("Date de fin", blank=True, null=True)
description = models.TextField("Description", blank=True)
image = models.ImageField("Image", blank=True, null=True,
upload_to="imgs/events/")
registration_open = models.BooleanField("Inscriptions ouvertes",
default=True)
old = models.BooleanField("Archiver (événement fini)", default=False)
class Meta:
verbose_name = "Événement"
def __str__(self):
return six.text_type(self.title)
@python_2_unicode_compatible
class EventCommentField(models.Model):
event = models.ForeignKey(Event, related_name="commentfields")
name = models.CharField("Champ", max_length=200)
fieldtype = models.CharField("Type", max_length=10,
choices=TYPE_COMMENT_FIELD, default="text")
default = models.TextField("Valeur par défaut", blank=True)
class Meta:
verbose_name = "Champ"
def __str__(self):
return six.text_type(self.name)
@python_2_unicode_compatible
class EventCommentValue(models.Model):
commentfield = models.ForeignKey(EventCommentField, related_name="values")
registration = models.ForeignKey("EventRegistration",
related_name="comments")
content = models.TextField("Contenu", blank=True, null=True)
def __str__(self):
return "Commentaire de %s" % self.commentfield
@python_2_unicode_compatible
class EventOption(models.Model):
event = models.ForeignKey(Event, related_name="options")
name = models.CharField("Option", max_length=200)
multi_choices = models.BooleanField("Choix multiples", default=False)
class Meta:
verbose_name = "Option"
def __str__(self):
return six.text_type(self.name)
@python_2_unicode_compatible
class EventOptionChoice(models.Model):
event_option = models.ForeignKey(EventOption, related_name="choices")
value = models.CharField("Valeur", max_length=200)
class Meta:
verbose_name = "Choix"
verbose_name_plural = "Choix"
def __str__(self):
return six.text_type(self.value)
@python_2_unicode_compatible
class EventRegistration(models.Model):
user = models.ForeignKey(User)
event = models.ForeignKey(Event)
options = models.ManyToManyField(EventOptionChoice)
filledcomments = models.ManyToManyField(EventCommentField,
through=EventCommentValue)
paid = models.BooleanField("A payé", default=False)
class Meta:
verbose_name = "Inscription"
unique_together = ("user", "event")
def __str__(self):
return "Inscription de %s à %s" % (six.text_type(self.user),
six.text_type(self.event.title))
@python_2_unicode_compatible
class Survey(models.Model):
title = models.CharField("Titre", max_length=200)
details = models.TextField("Détails", blank=True)
survey_open = models.BooleanField("Sondage ouvert", default=True)
old = models.BooleanField("Archiver (sondage fini)", default=False)
class Meta:
verbose_name = "Sondage"
def __str__(self):
return six.text_type(self.title)
@python_2_unicode_compatible
class SurveyQuestion(models.Model):
survey = models.ForeignKey(Survey, related_name="questions")
question = models.CharField("Question", max_length=200)
multi_answers = models.BooleanField("Choix multiples", default=False)
class Meta:
verbose_name = "Question"
def __str__(self):
return six.text_type(self.question)
@python_2_unicode_compatible
class SurveyQuestionAnswer(models.Model):
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers")
answer = models.CharField("Réponse", max_length=200)
class Meta:
verbose_name = "Réponse"
def __str__(self):
return six.text_type(self.answer)
@python_2_unicode_compatible
class SurveyAnswer(models.Model):
user = models.ForeignKey(User)
survey = models.ForeignKey(Survey)
answers = models.ManyToManyField(SurveyQuestionAnswer,
related_name="selected_by")
class Meta:
verbose_name = "Réponses"
unique_together = ("user", "survey")
def __str__(self):
return "Réponse de %s sondage %s" % (
self.user.get_full_name(),
self.survey.title)
@python_2_unicode_compatible
class CalendarSubscription(models.Model):
token = models.UUIDField()
user = models.OneToOneField(User)
other_shows = models.ManyToManyField(Spectacle)
subscribe_to_events = models.BooleanField(default=True)
subscribe_to_my_shows = models.BooleanField(default=True)
def __str__(self):
return "Calendrier de %s" % self.user.get_full_name()

View file

@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
from captcha.fields import ReCaptchaField
from django import forms
from django.forms import ModelForm
from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.contrib.auth.models import User
from gestioncof.petits_cours_models import PetitCoursDemande, PetitCoursAbility
class BaseMatieresFormSet(BaseInlineFormSet):
def clean(self):
super(BaseMatieresFormSet, self).clean()
if any(self.errors):
# Don't bother validating the formset unless each form is
# valid on its own
return
matieres = []
for i in range(0, self.total_form_count()):
form = self.forms[i]
if not form.cleaned_data:
continue
matiere = form.cleaned_data['matiere']
niveau = form.cleaned_data['niveau']
delete = form.cleaned_data['DELETE']
if not delete and (matiere, niveau) in matieres:
raise forms.ValidationError(
"Vous ne pouvez pas vous inscrire deux fois pour la "
"même matiere avec le même niveau.")
matieres.append((matiere, niveau))
class DemandeForm(ModelForm):
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
def __init__(self, *args, **kwargs):
super(DemandeForm, self).__init__(*args, **kwargs)
self.fields['matieres'].help_text = ''
class Meta:
model = PetitCoursDemande
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
'matieres', 'agrege_requis', 'niveau', 'remarques')
widgets = {'matieres': forms.CheckboxSelectMultiple}
MatieresFormSet = inlineformset_factory(
User,
PetitCoursAbility,
fields=("matiere", "niveau", "agrege"),
formset=BaseMatieresFormSet
)

View file

@ -1,178 +0,0 @@
# -*- coding: utf-8 -*-
from functools import reduce
from django.db import models
from django.db.models import Min
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
def choices_length(choices):
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
LEVELS_CHOICES = (
('college', _("Collège")),
('lycee', _("Lycée")),
('prepa1styear', _("Prépa 1ère année / L1")),
('prepa2ndyear', _("Prépa 2ème année / L2")),
('licence3', _("Licence 3")),
('other', _("Autre (préciser dans les commentaires)")),
)
class PetitCoursSubject(models.Model):
name = models.CharField(_("Matière"), max_length=30)
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
through="PetitCoursAbility")
class Meta:
verbose_name = "Matière de petits cours"
verbose_name_plural = "Matières des petits cours"
def __str__(self):
return self.name
class PetitCoursAbility(models.Model):
user = models.ForeignKey(User)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
niveau = models.CharField(_("Niveau"),
choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES))
agrege = models.BooleanField(_("Agrégé"), default=False)
class Meta:
verbose_name = "Compétence petits cours"
verbose_name_plural = "Compétences des petits cours"
def __str__(self):
return "{:s} - {!s} - {:s}".format(
self.user.username, self.matiere, self.niveau
)
class PetitCoursDemande(models.Model):
name = models.CharField(_("Nom/prénom"), max_length=200)
email = models.CharField(_("Adresse email"), max_length=300)
phone = models.CharField(_("Téléphone (facultatif)"),
max_length=20, blank=True)
quand = models.CharField(
_("Quand ?"),
help_text=_("Indiquez ici la période désirée pour les petits"
" cours (vacances scolaires, semaine, week-end)."),
max_length=300, blank=True)
freq = models.CharField(
_("Fréquence"),
help_text=_("Indiquez ici la fréquence envisagée "
"(hebdomadaire, 2 fois par semaine, ...)"),
max_length=300, blank=True)
lieu = models.CharField(
_("Lieu (si préférence)"),
help_text=_("Si vous avez avez une préférence sur le lieu."),
max_length=300, blank=True)
matieres = models.ManyToManyField(
PetitCoursSubject, verbose_name=_("Matières"),
related_name="demandes")
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
niveau = models.CharField(_("Niveau"),
default="",
choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES))
remarques = models.TextField(_("Remarques et précisions"), blank=True)
traitee = models.BooleanField(_("Traitée"), default=False)
traitee_par = models.ForeignKey(User, blank=True, null=True)
processed = models.DateTimeField(_("Date de traitement"),
blank=True, null=True)
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
def get_candidates(self, redo=False):
"""
Donne la liste des profs disponibles pour chaque matière de la demande.
- On ne donne que les agrégés si c'est demandé
- Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et
il ne faut pas proposer à nouveau des noms qui ont déjà été proposés
"""
for matiere in self.matieres.all():
candidates = PetitCoursAbility.objects.filter(
matiere=matiere,
niveau=self.niveau,
user__profile__is_cof=True,
user__profile__petits_cours_accept=True
)
if self.agrege_requis:
candidates = candidates.filter(agrege=True)
if redo:
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
already_proposed = [
attr.user
for attr in attrs
]
candidates = candidates.exclude(user__in=already_proposed)
candidates = candidates.order_by('?').select_related().all()
yield (matiere, candidates)
class Meta:
verbose_name = "Demande de petits cours"
verbose_name_plural = "Demandes de petits cours"
def __str__(self):
return "Demande {:d} du {:s}".format(
self.id, self.created.strftime("%d %b %Y")
)
class PetitCoursAttribution(models.Model):
user = models.ForeignKey(User)
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
rank = models.IntegerField("Rang dans l'email")
selected = models.BooleanField(_("Sélectionné par le demandeur"),
default=False)
class Meta:
verbose_name = "Attribution de petits cours"
verbose_name_plural = "Attributions de petits cours"
def __str__(self):
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
self.demande.id, self.user.username, self.matiere
)
class PetitCoursAttributionCounter(models.Model):
user = models.ForeignKey(User)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
count = models.IntegerField("Nombre d'envois", default=0)
@classmethod
def get_uptodate(cls, user, matiere):
"""
Donne le compteur de l'utilisateur pour cette matière. Si le compteur
n'existe pas encore, il est initialisé avec le minimum des valeurs des
compteurs de tout le monde.
"""
counter, created = cls.objects.get_or_create(
user=user, matiere=matiere)
if created:
mincount = (
cls.objects.filter(matiere=matiere).exclude(user=user)
.aggregate(Min('count'))
['count__min']
)
counter.count = mincount
counter.save()
return counter
class Meta:
verbose_name = "Compteur d'attribution de petits cours"
verbose_name_plural = "Compteurs d'attributions de petits cours"
def __str__(self):
return "{:d} demandes envoyées à {:s} pour {!s}".format(
self.count, self.user.username, self.matiere
)

View file

@ -1,352 +0,0 @@
# -*- coding: utf-8 -*-
import json
from datetime import datetime
from custommail.shortcuts import render_custom_mail
from django.shortcuts import render, get_object_or_404, redirect
from django.core import mail
from django.contrib.auth.models import User
from django.views.generic import ListView, DetailView
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from gestioncof.models import CofProfile
from gestioncof.petits_cours_models import (
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
PetitCoursAbility, PetitCoursSubject
)
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
from gestioncof.decorators import buro_required
from gestioncof.shared import lock_table, unlock_tables
class DemandeListView(ListView):
model = PetitCoursDemande
template_name = "petits_cours_demandes_list.html"
paginate_by = 20
def get_queryset(self):
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
class DemandeDetailView(DetailView):
model = PetitCoursDemande
template_name = "gestioncof/details_demande_petit_cours.html"
context_object_name = "demande"
def get_context_data(self, **kwargs):
context = super(DemandeDetailView, self).get_context_data(**kwargs)
obj = self.object
context['attributions'] = obj.petitcoursattribution_set.all()
return context
@buro_required
def traitement(request, demande_id, redo=False):
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
if demande.niveau == "other":
return _traitement_other(request, demande, redo)
if request.method == "POST":
return _traitement_post(request, demande)
proposals = {}
proposed_for = {}
unsatisfied = []
attribdata = {}
for matiere, candidates in demande.get_candidates(redo):
if candidates:
tuples = []
for candidate in candidates:
user = candidate.user
tuples.append((
candidate,
PetitCoursAttributionCounter.get_uptodate(user, matiere)
))
tuples = sorted(tuples, key=lambda c: c[1].count)
candidates, _ = zip(*tuples)
candidates = candidates[0:min(3, len(candidates))]
attribdata[matiere.id] = []
proposals[matiere] = []
for candidate in candidates:
user = candidate.user
proposals[matiere].append(user)
attribdata[matiere.id].append(user.id)
if user not in proposed_for:
proposed_for[user] = [matiere]
else:
proposed_for[user].append(matiere)
else:
unsatisfied.append(matiere)
return _finalize_traitement(request, demande, proposals,
proposed_for, unsatisfied, attribdata, redo)
@buro_required
def retraitement(request, demande_id):
return traitement(request, demande_id, redo=True)
def _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, redo=False, errors=None):
proposals = proposals.items()
proposed_for = proposed_for.items()
attribdata = list(attribdata.items())
proposed_mails = _generate_eleve_email(demande, proposed_for)
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
"proposals": proposals,
"unsatisfied": unsatisfied,
"extra":
'<textarea name="extra" '
'style="width:99%; height: 90px;">'
'</textarea>'
})
if errors is not None:
for error in errors:
messages.error(request, error)
return render(request, "gestioncof/traitement_demande_petit_cours.html",
{"demande": demande,
"unsatisfied": unsatisfied,
"proposals": proposals,
"proposed_for": proposed_for,
"proposed_mails": proposed_mails,
"mainmail": mainmail,
"attribdata": json.dumps(attribdata),
"redo": redo,
})
def _generate_eleve_email(demande, proposed_for):
return [
(
user,
render_custom_mail('petit-cours-mail-eleve', {
"demande": demande,
"matieres": matieres
})
)
for user, matieres in proposed_for
]
def _traitement_other_preparing(request, demande):
redo = "redo" in request.POST
unsatisfied = []
proposals = {}
proposed_for = {}
attribdata = {}
errors = []
for matiere, candidates in demande.get_candidates(redo):
if candidates:
candidates = dict([(candidate.user.id, candidate.user)
for candidate in candidates])
attribdata[matiere.id] = []
proposals[matiere] = []
for choice_id in range(min(3, len(candidates))):
choice = int(
request.POST["proposal-{:d}-{:d}"
.format(matiere.id, choice_id)]
)
if choice == -1:
continue
if choice not in candidates:
errors.append("Choix invalide pour la proposition {:d}"
"en {!s}".format(choice_id + 1, matiere))
continue
user = candidates[choice]
if user in proposals[matiere]:
errors.append("La proposition {:d} en {!s} est un doublon"
.format(choice_id + 1, matiere))
continue
proposals[matiere].append(user)
attribdata[matiere.id].append(user.id)
if user not in proposed_for:
proposed_for[user] = [matiere]
else:
proposed_for[user].append(matiere)
if not proposals[matiere]:
errors.append("Aucune proposition pour {!s}".format(matiere))
elif len(proposals[matiere]) < 3:
errors.append("Seulement {:d} proposition{:s} pour {!s}"
.format(
len(proposals[matiere]),
"s" if len(proposals[matiere]) > 1 else "",
matiere))
else:
unsatisfied.append(matiere)
return _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, errors=errors)
def _traitement_other(request, demande, redo):
if request.method == "POST":
if "preparing" in request.POST:
return _traitement_other_preparing(request, demande)
else:
return _traitement_post(request, demande)
proposals = {}
proposed_for = {}
unsatisfied = []
attribdata = {}
for matiere, candidates in demande.get_candidates(redo):
if candidates:
tuples = []
for candidate in candidates:
user = candidate.user
tuples.append((
candidate,
PetitCoursAttributionCounter.get_uptodate(user, matiere)
))
tuples = sorted(tuples, key=lambda c: c[1].count)
candidates, _ = zip(*tuples)
attribdata[matiere.id] = []
proposals[matiere] = []
for candidate in candidates:
user = candidate.user
proposals[matiere].append(user)
attribdata[matiere.id].append(user.id)
if user not in proposed_for:
proposed_for[user] = [matiere]
else:
proposed_for[user].append(matiere)
else:
unsatisfied.append(matiere)
proposals = proposals.items()
proposed_for = proposed_for.items()
return render(request,
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
{"demande": demande,
"unsatisfied": unsatisfied,
"proposals": proposals,
"proposed_for": proposed_for,
})
def _traitement_post(request, demande):
proposals = {}
proposed_for = {}
unsatisfied = []
extra = request.POST["extra"].strip()
redo = "redo" in request.POST
attribdata = request.POST["attribdata"]
attribdata = dict(json.loads(attribdata))
for matiere in demande.matieres.all():
if matiere.id not in attribdata:
unsatisfied.append(matiere)
else:
proposals[matiere] = []
for user_id in attribdata[matiere.id]:
user = User.objects.get(pk=user_id)
proposals[matiere].append(user)
if user not in proposed_for:
proposed_for[user] = [matiere]
else:
proposed_for[user].append(matiere)
proposals_list = proposals.items()
proposed_for = proposed_for.items()
proposed_mails = _generate_eleve_email(demande, proposed_for)
mainmail_object, mainmail_body = render_custom_mail(
"petits-cours-mail-demandeur",
{
"proposals": proposals_list,
"unsatisfied": unsatisfied,
"extra": extra
}
)
frommail = settings.MAIL_DATA['petits_cours']['FROM']
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
mails_to_send = []
for (user, (mail_object, body)) in proposed_mails:
msg = mail.EmailMessage(mail_object, body, frommail, [user.email],
[bccaddress], headers={'Reply-To': replyto})
mails_to_send.append(msg)
mails_to_send.append(mail.EmailMessage(mainmail_object, mainmail_body,
frommail, [demande.email],
[bccaddress],
headers={'Reply-To': replyto}))
connection = mail.get_connection(fail_silently=False)
connection.send_messages(mails_to_send)
lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
for matiere in proposals:
for rank, user in enumerate(proposals[matiere]):
counter = PetitCoursAttributionCounter.objects.get(user=user,
matiere=matiere)
counter.count += 1
counter.save()
attrib = PetitCoursAttribution(user=user, matiere=matiere,
demande=demande, rank=rank + 1)
attrib.save()
unlock_tables()
demande.traitee = True
demande.traitee_par = request.user
demande.processed = datetime.now()
demande.save()
return render(request,
"gestioncof/traitement_demande_petit_cours_success.html",
{"demande": demande,
"redo": redo,
})
@login_required
def inscription(request):
profile, created = CofProfile.objects.get_or_create(user=request.user)
if not profile.is_cof:
return redirect("cof-denied")
success = False
if request.method == "POST":
formset = MatieresFormSet(request.POST, instance=request.user)
if formset.is_valid():
formset.save()
profile.petits_cours_accept = "receive_proposals" in request.POST
profile.petits_cours_remarques = request.POST["remarques"]
profile.save()
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
PetitCoursSubject)
abilities = (
PetitCoursAbility.objects.filter(user=request.user).all()
)
for ability in abilities:
PetitCoursAttributionCounter.get_uptodate(
ability.user,
ability.matiere
)
unlock_tables()
success = True
formset = MatieresFormSet(instance=request.user)
else:
formset = MatieresFormSet(instance=request.user)
return render(request, "inscription-petit-cours.html",
{"formset": formset, "success": success,
"receive_proposals": profile.petits_cours_accept,
"remarques": profile.petits_cours_remarques})
@csrf_exempt
def demande(request):
success = False
if request.method == "POST":
form = DemandeForm(request.POST)
if form.is_valid():
form.save()
success = True
else:
form = DemandeForm()
return render(request, "demande-petit-cours.html", {"form": form,
"success": success})
@csrf_exempt
def demande_raw(request):
success = False
if request.method == "POST":
form = DemandeForm(request.POST)
if form.is_valid():
form.save()
success = True
else:
form = DemandeForm()
return render(request, "demande-petit-cours-raw.html",
{"form": form, "success": success})

View file

@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django.contrib.sites.models import Site
from django.conf import settings
from django_cas_ng.backends import CASBackend
from django_cas_ng.utils import get_cas_client
from django.contrib.auth import get_user_model
from django.db import connection
from gestioncof.models import CofProfile
User = get_user_model()
class COFCASBackend(CASBackend):
def authenticate_cas(self, ticket, service, request):
"""Verifies CAS ticket and gets or creates User object"""
client = get_cas_client(service_url=service)
username, attributes, _ = client.verify_ticket(ticket)
if attributes:
request.session['attributes'] = attributes
if not username:
return None
# Le CAS de l'ENS accepte les logins avec des espaces au début
# et à la fin, ainsi quavec une casse variable. On normalise pour
# éviter les doublons.
username = username.strip().lower()
profiles = CofProfile.objects.filter(login_clipper=username)
if len(profiles) > 0:
profile = profiles.order_by('-is_cof')[0]
user = profile.user
return user
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# user will have an "unusable" password
user = User.objects.create_user(username, '')
user.save()
return user
def authenticate(self, ticket, service, request):
"""Authenticates CAS ticket and retrieves user data"""
user = self.authenticate_cas(ticket, service, request)
if user is None:
return user
try:
profile = user.profile
except CofProfile.DoesNotExist:
profile, created = CofProfile.objects.get_or_create(user=user)
profile.save()
if not profile.login_clipper:
profile.login_clipper = user.username
profile.save()
if not user.email:
user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper
user.save()
if profile.is_buro and not user.is_staff:
user.is_staff = True
user.save()
return user
def context_processor(request):
'''Append extra data to the context of the given request'''
data = {
"user": request.user,
"site": Site.objects.get_current(),
}
return data
def lock_table(*models):
query = "LOCK TABLES "
for i, model in enumerate(models):
table = model._meta.db_table
if i > 0:
query += ", "
query += "%s WRITE" % table
cursor = connection.cursor()
cursor.execute(query)
row = cursor.fetchone()
return row
def unlock_tables(*models):
cursor = connection.cursor()
cursor.execute("UNLOCK TABLES")
row = cursor.fetchone()
return row
unlock_table = unlock_tables

Some files were not shown because too many files have changed in this diff Show more