Compare commits

...

598 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
538 changed files with 14543 additions and 23814 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

4
.gitignore vendored
View file

@ -5,6 +5,7 @@ cof/settings.py
settings.py
*~
venv/
.venv/
.vagrant
/src
media/
@ -18,4 +19,5 @@ media/
.cache
# VSCode
.vscode/
.vscode/
.direnv

View file

@ -1,8 +1,7 @@
image: "python:3.5"
image: "python:3.7"
variables:
# GestioCOF settings
DJANGO_SETTINGS_MODULE: "cof.settings.prod"
DBHOST: "postgres"
REDIS_HOST: "redis"
REDIS_PASSWD: "dummy"
@ -18,23 +17,23 @@ variables:
# psql password authentication
PGPASSWORD: $POSTGRES_PASSWORD
test:
stage: test
# apps to check migrations for
MIGRATION_APPS: "bda bds cofcms clubs events gestioncof kfet kfetauth kfetcms open petitscours shared"
.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
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
- 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
script:
- coverage run manage.py test --parallel
after_script:
- coverage report
services:
- postgres:9.6
- postgres:11.7
- redis:latest
cache:
key: test
@ -44,17 +43,32 @@ test:
# Keep this disabled for now, as it may kill GitLab...
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
coftest:
stage: test
extends: .test_template
variables:
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
script:
- 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:
image: python:3.6
stage: test
before_script:
- mkdir -p vendor/pip
- pip install --upgrade black isort flake8
script:
- black --check .
- isort --recursive --check-only --diff bda bds clubs cof events gestioncof kfet petitscours provisioning shared utils
- isort --check --diff .
# Print errors only
- flake8 --exit-zero bda bds clubs cof events gestioncof kfet petitscours provisioning shared utils
- flake8 --exit-zero bda bds clubs gestioasso events gestioncof kfet petitscours provisioning shared
cache:
key: linters
paths:
@ -63,16 +77,18 @@ linters:
# 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
- cp cof/settings/secret_example.py cof/settings/secret.py
- pip install --upgrade -r requirements-prod.txt
- 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
script: python manage.py makemigrations --dry-run --check $MIGRATION_APPS
services:
# this should not be necessary…
- postgres:9.6
- postgres:11.7
cache:
key: migration_checks
paths:

View file

@ -48,7 +48,7 @@ if type isort &>/dev/null; then
ISORT_OUTPUT="/tmp/gc-isort-output.log"
touch $ISORT_OUTPUT
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort --check-only &>$ISORT_OUTPUT; then
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

View file

@ -1,47 +0,0 @@
* Le FUTUR ! (pas prêt pour la prod)
- Nouveau module de gestion des événements
- Nouveau module BDS
- Nouveau module clubs
* 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

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

View file

@ -1,4 +1,4 @@
# 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)
@ -38,11 +38,11 @@ Vous pouvez maintenant installer les dépendances Python depuis le fichier
pip install -U pip # parfois nécessaire la première fois
pip install -r requirements-devel.txt
Pour terminer, copier le fichier `cof/settings/secret_example.py` vers
`cof/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
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 cof/settings/secret.py
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

View file

@ -1 +0,0 @@
- Changer les urls dans les mails "bda-revente" et "bda-shotgun"

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,363 +0,0 @@
from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail
from dal.autocomplete import ModelSelect2
from django import forms
from django.contrib import admin
from django.db.models import Count, Q, Sum
from django.template.defaultfilters import pluralize
from django.utils import timezone
from bda.models import (
Attribution,
CategorieSpectacle,
ChoixSpectacle,
Participant,
Quote,
Salle,
Spectacle,
SpectacleRevente,
Tirage,
)
class ReadOnlyMixin(object):
readonly_fields_update = ()
def get_readonly_fields(self, request, obj=None):
readonly_fields = super().get_readonly_fields(request, obj)
if obj is None:
return readonly_fields
else:
return readonly_fields + self.readonly_fields_update
class ChoixSpectacleAdminForm(forms.ModelForm):
class Meta:
widgets = {
"participant": ModelSelect2(url="bda-participant-autocomplete"),
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
}
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
form = ChoixSpectacleAdminForm
sortable_field_name = "priority"
class AttributionTabularAdminForm(forms.ModelForm):
listing = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
spectacles = Spectacle.objects.select_related("location")
if self.listing is not None:
spectacles = spectacles.filter(listing=self.listing)
self.fields["spectacle"].queryset = spectacles
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
listing = False
class WithListingAttributionTabularAdminForm(AttributionTabularAdminForm):
listing = True
class AttributionInline(admin.TabularInline):
model = Attribution
extra = 0
listing = None
def get_queryset(self, request):
qs = super().get_queryset(request)
if self.listing is not None:
qs = qs.filter(spectacle__listing=self.listing)
return qs
class WithListingAttributionInline(AttributionInline):
exclude = ("given",)
form = WithListingAttributionTabularAdminForm
listing = True
verbose_name_plural = "Attributions sur listing"
class WithoutListingAttributionInline(AttributionInline):
form = WithoutListingAttributionTabularAdminForm
listing = False
verbose_name_plural = "Attributions hors listing"
class ParticipantAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
"location"
)
class ParticipantPaidFilter(admin.SimpleListFilter):
"""
Permet de filtrer les participants sur s'ils ont payé leurs places ou pas
"""
title = "A payé"
parameter_name = "paid"
def lookups(self, request, model_admin):
return ((True, "Oui"), (False, "Non"))
def queryset(self, request, queryset):
return queryset.filter(paid=self.value())
class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
def get_queryset(self, request):
return self.model.objects.annotate_paid().annotate(
nb_places=Count("attributions"),
remain=Sum(
"attribution__spectacle__price", filter=Q(attribution__paid=False)
),
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 paid(self, obj):
return obj.paid
paid.short_description = "A payé"
paid.boolean = True
paid.admin_order_field = "paid"
def total(self, obj):
tot = obj.total
if tot:
return "%.02f" % tot
else:
return "0 €"
total.admin_order_field = "total"
total.short_description = "Total des places"
def remain(self, obj):
rem = obj.remain
if rem:
return "%.02f" % rem
else:
return "0 €"
remain.admin_order_field = "remain"
remain.short_description = "Reste à payer"
list_display = ("user", "nb_places", "total", "paid", "remain", "tirage")
list_filter = (ParticipantPaidFilter, "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", "paid")
readonly_fields_update = ("user", "tirage")
form = ParticipantAdminForm
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 __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "spectacle" in self.fields:
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
"location"
)
if "participant" in self.fields:
self.fields["participant"].queryset = Participant.objects.select_related(
"user", "tirage"
)
def clean(self):
cleaned_data = super().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(ReadOnlyMixin, admin.ModelAdmin):
list_display = ("id", "spectacle", "participant", "given", "paid")
search_fields = (
"spectacle__title",
"participant__user__username",
"participant__user__first_name",
"participant__user__last_name",
)
form = AttributionAdminForm
readonly_fields_update = ("spectacle", "participant")
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = ChoixSpectacleAdminForm
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 SpectacleReventeAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
"user", "tirage"
)
self.fields["seller"].queryset = Participant.objects.select_related(
"user", "tirage"
)
self.fields["soldTo"].queryset = Participant.objects.select_related(
"user", "tirage"
)
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
form = SpectacleReventeAdminForm
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.reset(new_date=timezone.now() - timedelta(hours=1))
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,103 +0,0 @@
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 * max(choice.priority for choice in choices)
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,184 +0,0 @@
from django import forms
from django.forms.models import BaseInlineFormSet
from django.template import loader
from django.utils import timezone
from bda.models import Attribution, Spectacle, SpectacleRevente
class InscriptionInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.instance is a Participant object
tirage = self.instance.tirage
# set once for all "spectacle" field choices
# - restrict choices to the spectacles of this tirage
# - force_choices avoid many db requests
spectacles = tirage.spectacle_set.select_related("location")
choices = [(sp.pk, str(sp)) for sp in spectacles]
self.force_choices("spectacle", choices)
def force_choices(self, name, choices):
"""Set choices of a field.
As ModelChoiceIterator (default use to get choices of a
ModelChoiceField), it appends an empty selection if requested.
"""
for form in self.forms:
field = form.fields[name]
if field.empty_label is not None:
field.choices = [("", field.empty_label)] + choices
else:
field.choices = choices
class TokenForm(forms.Form):
token = forms.CharField(widget=forms.widgets.Textarea())
class TemplateLabelField(forms.ModelMultipleChoiceField):
"""
Extends ModelMultipleChoiceField to offer two more customization options :
- `label_from_instance` can be used with a template file
- the widget rendering template can be specified with `option_template_name`
"""
def __init__(
self,
label_template_name=None,
context_object_name="obj",
option_template_name=None,
*args,
**kwargs
):
super().__init__(*args, **kwargs)
self.label_template_name = label_template_name
self.context_object_name = context_object_name
if option_template_name is not None:
self.widget.option_template_name = option_template_name
def label_from_instance(self, obj):
if self.label_template_name is None:
return super().label_from_instance(obj)
else:
return loader.render_to_string(
self.label_template_name, context={self.context_object_name: obj}
)
# Formulaires pour revente_manage
class ResellForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["attributions"] = TemplateLabelField(
queryset=participant.attribution_set.filter(
spectacle__date__gte=timezone.now()
)
.exclude(revente__seller=participant)
.select_related("spectacle", "spectacle__location", "participant__user"),
widget=forms.CheckboxSelectMultiple,
required=False,
label_template_name="bda/forms/attribution_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="attribution",
)
class AnnulForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["reventes"] = TemplateLabelField(
label="",
queryset=participant.original_shows.filter(
attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True
)
.select_related(
"attribution__spectacle", "attribution__spectacle__location"
)
.order_by("-date"),
widget=forms.CheckboxSelectMultiple,
required=False,
label_template_name="bda/forms/revente_self_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="revente",
)
class SoldForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["reventes"] = TemplateLabelField(
queryset=participant.original_shows.filter(soldTo__isnull=False)
.exclude(soldTo=participant)
.select_related(
"attribution__spectacle", "attribution__spectacle__location"
),
widget=forms.CheckboxSelectMultiple,
label_template_name="bda/forms/revente_sold_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="revente",
)
# Formulaire pour revente_subscribe
class InscriptionReventeForm(forms.Form):
def __init__(self, tirage, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["spectacles"] = TemplateLabelField(
queryset=tirage.spectacle_set.select_related("location").filter(
date__gte=timezone.now()
),
widget=forms.CheckboxSelectMultiple,
required=False,
label_template_name="bda/forms/spectacle_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="spectacle",
)
# Formulaires pour revente_tirages
class ReventeTirageAnnulForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["reventes"] = TemplateLabelField(
queryset=participant.entered.filter(soldTo__isnull=True).select_related(
"attribution__spectacle", "seller__user"
),
widget=forms.CheckboxSelectMultiple,
required=False,
label_template_name="bda/forms/revente_other_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="revente",
)
class ReventeTirageForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["reventes"] = TemplateLabelField(
queryset=(
SpectacleRevente.objects.filter(
notif_sent=True,
shotgun=False,
tirage_done=False,
attribution__spectacle__tirage=participant.tirage,
)
.exclude(confirmed_entry=participant)
.select_related("attribution__spectacle")
),
widget=forms.CheckboxSelectMultiple,
required=False,
label_template_name="bda/forms/revente_other_label_table.html",
option_template_name="bda/forms/checkbox_table.html",
context_object_name="revente",
)

View file

@ -1,101 +0,0 @@
"""
Crée deux tirages de test et y inscrit les utilisateurs
"""
import os
import random
from django.contrib.auth.models import User
from django.utils import timezone
from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage
from bda.views import do_tirage
from gestioncof.management.base import MyBaseCommand
# 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,49 +0,0 @@
"""
Gestion en ligne de commande des reventes.
"""
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:
# Le spectacle est bientôt et on a pas encore envoyé de mail :
# on met la place au shotgun et on prévient.
if revente.is_urgent and not revente.notif_sent:
if revente.can_notif:
self.stdout.write(str(now))
revente.mail_shotgun()
self.stdout.write(
"Mails de disponibilité immédiate envoyés "
"pour la revente [%s]" % revente
)
# Le spectacle est dans plus longtemps : on prévient
elif revente.can_notif and not revente.notif_sent:
self.stdout.write(str(now))
revente.send_notif()
self.stdout.write(
"Mails d'inscription à la revente [%s] envoyés" % revente
)
# On fait le tirage
elif now >= revente.date_tirage and not revente.tirage_done:
self.stdout.write(str(now))
winner = revente.tirage()
self.stdout.write("Tirage effectué pour la revente [%s]" % revente)
if winner:
self.stdout.write("Gagnant : %s" % winner.user)
else:
self.stdout.write("Pas de gagnant ; place au shotgun")

View file

@ -1,33 +0,0 @@
"""
Gestion en ligne de commande des mails de rappel.
"""
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.\n"
"Ne 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,206 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
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", on_delete=models.CASCADE),
),
],
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, on_delete=models.CASCADE
),
),
migrations.AddField(
model_name="choixspectacle",
name="participant",
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
),
migrations.AddField(
model_name="choixspectacle",
name="spectacle",
field=models.ForeignKey(
related_name="participants",
to="bda.Spectacle",
on_delete=models.CASCADE,
),
),
migrations.AddField(
model_name="attribution",
name="participant",
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
),
migrations.AddField(
model_name="attribution",
name="spectacle",
field=models.ForeignKey(
related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE
),
),
migrations.AlterUniqueTogether(
name="choixspectacle", unique_together=set([("participant", "spectacle")])
),
]

View file

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
from django.utils import timezone
def fill_tirage_fields(apps, schema_editor):
"""
Create a `Tirage` to fill new field `tirage` of `Participant`
and `Spectacle` already existing.
"""
Participant = apps.get_model("bda", "Participant")
Spectacle = apps.get_model("bda", "Spectacle")
Tirage = apps.get_model("bda", "Tirage")
# These querysets only contains instances not linked to any `Tirage`.
participants = Participant.objects.filter(tirage=None)
spectacles = Spectacle.objects.filter(tirage=None)
if not participants.count() and not spectacles.count():
# No need to create a "trash" tirage.
return
tirage = Tirage.objects.create(
title="Tirage de test (migration)",
active=False,
ouverture=timezone.now(),
fermeture=timezone.now(),
)
participants.update(tirage=tirage)
spectacles.update(tirage=tirage)
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.AlterField(
model_name="participant",
name="user",
field=models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
# Create fields `spectacle` for `Participant` and `Spectacle` models.
# These fields are not nullable, but we first create them as nullable
# to give a default value for existing instances of these models.
migrations.AddField(
model_name="participant",
name="tirage",
field=models.ForeignKey(
to="bda.Tirage", null=True, on_delete=models.CASCADE
),
),
migrations.AddField(
model_name="spectacle",
name="tirage",
field=models.ForeignKey(
to="bda.Tirage", null=True, on_delete=models.CASCADE
),
),
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
migrations.AlterField(
model_name="participant",
name="tirage",
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
),
migrations.AlterField(
model_name="spectacle",
name="tirage",
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
),
]

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", "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,27 +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,34 +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,100 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
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",
on_delete=models.CASCADE,
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", on_delete=models.CASCADE),
),
]

View file

@ -1,110 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
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,87 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
from django.db import migrations, models
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", on_delete=models.CASCADE, related_name="revente"
),
),
migrations.AddField(
model_name="spectaclerevente",
name="seller",
field=models.ForeignKey(
to="bda.Participant",
on_delete=models.CASCADE,
verbose_name="Vendeur",
related_name="original_shows",
),
),
migrations.AddField(
model_name="spectaclerevente",
name="soldTo",
field=models.ForeignKey(
to="bda.Participant",
on_delete=models.CASCADE,
verbose_name="Vendue à",
null=True,
blank=True,
),
),
]

View file

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import timedelta
from django.db import migrations, models
from django.utils import timezone
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,19 +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,31 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("bda", "0011_tirage_appear_catalogue")]
operations = [
migrations.RenameField(
model_name="spectaclerevente",
old_name="answered_mail",
new_name="confirmed_entry",
),
migrations.AlterField(
model_name="spectaclerevente",
name="confirmed_entry",
field=models.ManyToManyField(
blank=True, related_name="entered", to="bda.Participant"
),
),
migrations.AddField(
model_name="spectaclerevente",
name="notif_time",
field=models.DateTimeField(
blank=True, verbose_name="Moment d'envoi de la notification", null=True
),
),
]

View file

@ -1,51 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def swap_double_choice(apps, schema_editor):
choices = apps.get_model("bda", "ChoixSpectacle").objects
choices.filter(double_choice="double").update(double_choice="tmp")
choices.filter(double_choice="autoquit").update(double_choice="double")
choices.filter(double_choice="tmp").update(double_choice="autoquit")
class Migration(migrations.Migration):
dependencies = [("bda", "0011_tirage_appear_catalogue")]
operations = [
# Temporarily allow an extra "tmp" value for the `double_choice` field
migrations.AlterField(
model_name="choixspectacle",
name="double_choice",
field=models.CharField(
verbose_name="Nombre de places",
max_length=10,
default="1",
choices=[
("tmp", "tmp"),
("1", "1 place"),
("double", "2 places si possible, 1 sinon"),
("autoquit", "2 places sinon rien"),
],
),
),
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
migrations.AlterField(
model_name="choixspectacle",
name="double_choice",
field=models.CharField(
verbose_name="Nombre de places",
max_length=10,
default="1",
choices=[
("1", "1 place"),
("double", "2 places si possible, 1 sinon"),
("autoquit", "2 places sinon rien"),
],
),
),
]

View file

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-05-24 19:23
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")]
operations = []

View file

@ -1,31 +0,0 @@
# Generated by Django 2.2 on 2019-06-03 19:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("bda", "0013_merge_20180524_2123")]
operations = [
migrations.AddField(
model_name="attribution",
name="paid",
field=models.BooleanField(default=False, verbose_name="Payée"),
),
migrations.AddField(
model_name="attribution",
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",
),
),
]

View file

@ -1,37 +0,0 @@
# Generated by Django 2.2 on 2019-06-03 19:30
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.db import migrations
def set_attr_payment(apps, schema_editor):
Attribution = apps.get_model("bda", "Attribution")
for attr in Attribution.objects.all():
attr.paid = attr.participant.paid
attr.paymenttype = attr.participant.paymenttype
attr.save()
def set_participant_payment(apps, schema_editor):
Participant = apps.get_model("bda", "Participant")
for part in Participant.objects.all():
attr_set = part.attribution_set
part.paid = attr_set.exists() and not attr_set.filter(paid=False).exists()
try:
# S'il n'y a qu'un seul type de paiement, on le set
part.paymenttype = (
attr_set.values_list("paymenttype", flat=True).distinct().get()
)
# Sinon, whatever
except (ObjectDoesNotExist, MultipleObjectsReturned):
pass
part.save()
class Migration(migrations.Migration):
dependencies = [("bda", "0014_attribution_paid_field")]
operations = [
migrations.RunPython(set_attr_payment, set_participant_payment, atomic=True)
]

View file

@ -1,13 +0,0 @@
# Generated by Django 2.2 on 2019-06-03 19:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("bda", "0015_move_bda_payment")]
operations = [
migrations.RemoveField(model_name="participant", name="paid"),
migrations.RemoveField(model_name="participant", name="paymenttype"),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.2 on 2019-09-18 16:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("bda", "0016_delete_participant_paid")]
operations = [
migrations.AddField(
model_name="participant",
name="accepte_charte",
field=models.BooleanField(
default=False, verbose_name="A accepté la charte BdA"
),
)
]

View file

@ -1,469 +0,0 @@
import calendar
import random
from datetime import timedelta
from custommail.models import CustomMail
from custommail.shortcuts import send_mass_custom_mail
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core import mail
from django.db import models
from django.db.models import Count, Exists
from django.utils import formats, timezone
def get_generic_user():
generic, _ = User.objects.get_or_create(
username="bda_generic",
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"},
)
return generic
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, on_delete=models.CASCADE, blank=True, null=True
)
date = models.DateTimeField("Date & heure")
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
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, on_delete=models.CASCADE)
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 Exception:
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 + le BdA
members = list(
User.objects.filter(participant__attributions=self)
.annotate(nb_attr=Count("id"))
.order_by()
)
bda_generic = get_generic_user()
bda_generic.nb_attr = 1
members.append(bda_generic)
# On écrit un mail personnalisé à chaque participant
datatuple = [
(
"bda-rappel",
{"member": member, "nb_attr": member.nb_attr, "show": self},
settings.MAIL_DATA["rappels"]["FROM"],
[member.email],
)
for member in members
]
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
@property
def is_past(self):
return self.date < timezone.now()
class Quote(models.Model):
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
text = models.TextField("Citation")
author = models.CharField("Auteur", max_length=200)
PAYMENT_TYPES = (
("cash", "Cash"),
("cb", "CB"),
("cheque", "Chèque"),
("autre", "Autre"),
)
class Attribution(models.Model):
participant = models.ForeignKey("Participant", on_delete=models.CASCADE)
spectacle = models.ForeignKey(
Spectacle, on_delete=models.CASCADE, related_name="attribues"
)
given = models.BooleanField("Donnée", default=False)
paid = models.BooleanField("Payée", default=False)
paymenttype = models.CharField(
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
)
def __str__(self):
return "%s -- %s, %s" % (
self.participant.user,
self.spectacle.title,
self.spectacle.date,
)
class ParticipantPaidQueryset(models.QuerySet):
"""
Un manager qui annote le queryset avec un champ `paid`,
indiquant si un participant a payé toutes ses attributions.
"""
def annotate_paid(self):
# OuterRef permet de se référer à un champ d'un modèle non encore fixé
# Voir:
# https://docs.djangoproject.com/en/2.2/ref/models/expressions/#django.db.models.OuterRef
unpaid = Attribution.objects.filter(
participant=models.OuterRef("pk"), paid=False
)
return self.annotate(paid=~Exists(unpaid))
class Participant(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
choices = models.ManyToManyField(
Spectacle, through="ChoixSpectacle", related_name="chosen_by"
)
attributions = models.ManyToManyField(
Spectacle, through="Attribution", related_name="attributed_to"
)
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
accepte_charte = models.BooleanField("A accepté la charte BdA", default=False)
choicesrevente = models.ManyToManyField(
Spectacle, related_name="subscribed", blank=True
)
objects = ParticipantPaidQueryset.as_manager()
def __str__(self):
return "%s - %s" % (self.user, self.tirage.title)
DOUBLE_CHOICES = (
("1", "1 place"),
("double", "2 places si possible, 1 sinon"),
("autoquit", "2 places sinon rien"),
)
class ChoixSpectacle(models.Model):
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
spectacle = models.ForeignKey(
Spectacle, on_delete=models.CASCADE, 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 SpectacleRevente(models.Model):
attribution = models.OneToOneField(
Attribution, on_delete=models.CASCADE, related_name="revente"
)
date = models.DateTimeField("Date de mise en vente", default=timezone.now)
confirmed_entry = models.ManyToManyField(
Participant, related_name="entered", blank=True
)
seller = models.ForeignKey(
Participant,
on_delete=models.CASCADE,
verbose_name="Vendeur",
related_name="original_shows",
)
soldTo = models.ForeignKey(
Participant,
on_delete=models.CASCADE,
verbose_name="Vendue à",
blank=True,
null=True,
)
notif_sent = models.BooleanField("Notification envoyée", default=False)
notif_time = models.DateTimeField(
"Moment d'envoi de la notification", blank=True, null=True
)
tirage_done = models.BooleanField("Tirage effectué", default=False)
shotgun = models.BooleanField("Disponible immédiatement", default=False)
####
# Some class attributes
###
# TODO : settings ?
# Temps minimum entre le tirage et le spectacle
min_margin = timedelta(days=5)
# Temps entre la création d'une revente et l'envoi du mail
remorse_time = timedelta(hours=1)
# Temps min/max d'attente avant le tirage
max_wait_time = timedelta(days=3)
min_wait_time = timedelta(days=1)
@property
def real_notif_time(self):
if self.notif_time:
return self.notif_time
else:
return self.date + self.remorse_time
@property
def date_tirage(self):
"""Renvoie la date du tirage au sort de la revente."""
remaining_time = (
self.attribution.spectacle.date - self.real_notif_time - self.min_margin
)
delay = min(remaining_time, self.max_wait_time)
return self.real_notif_time + delay
@property
def is_urgent(self):
"""
Renvoie True iff la revente doit être mise au shotgun directement.
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
"""
spectacle_date = self.attribution.spectacle.date
return spectacle_date <= timezone.now() + self.min_margin + self.min_wait_time
@property
def can_notif(self):
return timezone.now() >= self.date + self.remorse_time
def __str__(self):
return "%s -- %s" % (self.seller, self.attribution.spectacle.title)
class Meta:
verbose_name = "Revente"
def reset(self, new_date=timezone.now()):
"""Réinitialise la revente pour permettre une remise sur le marché"""
self.seller = self.attribution.participant
self.date = new_date
self.confirmed_entry.clear()
self.soldTo = None
self.notif_sent = False
self.notif_time = None
self.tirage_done = False
self.shotgun = False
self.save()
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.notif_time = timezone.now()
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
self.notif_time = timezone.now()
# Flag inutile, sauf si l'horloge interne merde
self.tirage_done = True
self.shotgun = True
self.save()
def tirage(self, send_mails=True):
"""
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.confirmed_entry.all())
spectacle = self.attribution.spectacle
seller = self.seller
winner = None
if inscrits:
# Envoie un mail au gagnant et au vendeur
winner = random.choice(inscrits)
self.soldTo = winner
if send_mails:
mails = []
context = {
"acheteur": winner.user,
"vendeur": seller.user,
"show": spectacle,
}
c_mails_qs = CustomMail.objects.filter(
shortname__in=[
"bda-revente-winner",
"bda-revente-loser",
"bda-revente-seller",
]
)
c_mails = {cm.shortname: cm for cm in c_mails_qs}
mails.append(
c_mails["bda-revente-winner"].get_message(
context,
from_email=settings.MAIL_DATA["revente"]["FROM"],
to=[winner.user.email],
)
)
mails.append(
c_mails["bda-revente-seller"].get_message(
context,
from_email=settings.MAIL_DATA["revente"]["FROM"],
to=[seller.user.email],
reply_to=[winner.user.email],
)
)
# Envoie un mail aux perdants
for inscrit in inscrits:
if inscrit != winner:
new_context = dict(context)
new_context["acheteur"] = inscrit.user
mails.append(
c_mails["bda-revente-loser"].get_message(
new_context,
from_email=settings.MAIL_DATA["revente"]["FROM"],
to=[inscrit.user.email],
)
)
mail_conn = mail.get_connection()
mail_conn.send_messages(mails)
# Si personne ne veut de la place, elle part au shotgun
else:
self.shotgun = True
self.tirage_done = True
self.save()
return winner

View file

@ -1,125 +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;
}
/** JQuery-Confirm box **/
.jconfirm .jconfirm-bg {
background-color: rgb(0,0,0,0.6) !important;
}
.jconfirm .jconfirm-box {
padding:0;
border-radius:0 !important;
font-family:Roboto;
}
.jconfirm .jconfirm-box .content-pane {
border-bottom:1px solid #ddd;
margin: 0px !important;
}
.jconfirm .jconfirm-box .content {
padding: 5px;
}
.jconfirm .jconfirm-box .content-pane {
border-bottom:1px solid #ddd;
margin: 0px !important;
}
.jconfirm .jconfirm-box .content {
padding: 10px;
}
.jconfirm .jconfirm-box .content a,
.jconfirm .jconfirm-box .content a:hover {
color: #D81138;
font-weight: bold;
}
.jconfirm .jconfirm-box .buttons {
margin-top:-6px; /* j'arrive pas à voir pk y'a un espace au dessus sinon... */
padding:0;
height:40px;
}
.jconfirm .jconfirm-box .buttons button {
min-width:40px;
height:100%;
margin:0;
margin:0 !important;
border-radius: 0 !important;
}
.jconfirm .jconfirm-box .buttons button:first-child:focus,
.jconfirm .jconfirm-box .buttons button:first-child:hover {
color:#FFF !important;
background:forestgreen !important;
}
.jconfirm .jconfirm-box .buttons button:nth-child(2):focus,
.jconfirm .jconfirm-box .buttons button:nth-child(2):hover {
color:#FFF !important;
background:#D93A32 !important;
}
.jconfirm .jconfirm-box div.title-c .title {
display: block;
padding:0 15px;
height:40px;
line-height:40px;
font-family:Dosis;
font-size:20px;
font-weight:bold;
color:#FFF;
background-color:rgb(222, 130, 107);
}

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 "bda/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,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,50 +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">
$(function(){
$("table.etat-bda").stupidtable();
});
</script>
{% endblock %}

View file

@ -1 +0,0 @@
{% include 'bda/forms/spectacle_label_table.html' with spectacle=attribution.spectacle %}

View file

@ -1,4 +0,0 @@
<tr>
<td><input type="{{ widget.type }}" name="{{ widget.name }}" {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}></td>
{{ widget.label }}
</tr>

View file

@ -1 +0,0 @@
<td data-sort-value="{{ revente.date_tirage | date:"U" }}">{{ revente.date_tirage }}</td>

View file

@ -1,3 +0,0 @@
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
{% with user=revente.seller.user %} <td>{{user.first_name}} {{user.last_name}}</td> {% endwith%}
{% include 'bda/forms/date_tirage.html' %}

View file

@ -1,2 +0,0 @@
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
{% include 'bda/forms/date_tirage.html' %}

View file

@ -1,4 +0,0 @@
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
{% with user=revente.soldTo.user %}
<td><a href="mailto:{{ user.email }}">{{user.first_name}} {{user.last_name}}</a></td>
{% endwith %}

View file

@ -1,4 +0,0 @@
<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.price |stringformat:".3f" }}">{{ spectacle.price |floatformat }}€</td>

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,169 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block extra_head %}
<script type="text/javascript" src="{% static 'vendor/jquery/jquery-ui.min.js' %}" ></script>
<script type="text/javascript" src="{% static "vendor/jquery/jquery-confirm.js" %}"></script>
<script type="text/javascript" src="{% static 'gestioncof/vendor/jquery.ui.touch-punch.min.js' %}" ></script>
<link type="text/css" rel="stylesheet" href="{% static 'vendor/jquery/jquery-confirm.css' %}">
<link type="text/css" rel="stylesheet" href="{% static 'vendor/jquery/jquery-ui.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static 'bda/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);
});
// Cloning <select> element doesn't properly propagate the default
// selected <option>, so we set it manually.
newElement.find('select').each(function (index, select) {
var defaultValue = $(select).find('option[selected]').val();
if (typeof defaultValue !== 'undefined') {
$(select).val(defaultValue);
}
});
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);
}
} else {
// Reset the default values
var selects = $(form).find("select");
$(selects[0]).val("");
$(selects[1]).val("1");
}
// 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" id="bda-inscr" 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>
{% if not charte %}
<script>
(function ($) {
var charte_ok = false ;
function link_charte() {
$.confirm({
title: 'Charte du BdA',
columnClass: 'col-md-6 col-md-offset-3',
content: `
<div>
En vous inscrivant à ce tirage du Bureau des Arts, vous vous engagez à \
respecter la charte du BdA:</br> \
<a target="_blank" href='https://bda.ens.fr/lequipe/charte-bda/'>https://bda.ens.fr/lequipe/charte-bda/</a>
</div>`,
backgroundDismiss: true,
opacity: 1,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirmButton: '<span class="glyphicon glyphicon-ok"></span>',
cancelButton: '<span class="glyphicon glyphicon-remove"></span>',
confirm: function() {
charte_ok = true ;
$("#bda_form").submit();
},
});
}
$(document).ready(function($) {
$("#bda_form").submit(function(e) {
if (!charte_ok) {
e.preventDefault();
link_charte();
}
})
})
})(django.jQuery);
</script>
{% endif %}
{% endblock %}

View file

@ -1,48 +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>
{% endif %}
<div class="empty-form">
{% 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 %}
</div>
<hr \>
<p>
<em>Note :</em> le template de ce mail peut être modifié à
<a href="{% url 'admin:custommail_custommail_change' custommail.pk %}">cette adresse</a>
</p>
<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,70 +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>
<div>
<div>
<button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button>
<pre id="export-mails" style="display:none">{% spaceless %}
{% for participant in participants %}{{ participant.email }}, {% endfor %}
{% endspaceless %}</pre>
</div>
<div>
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
<pre id="export-salle" style="display:none">{% spaceless %}
{% for participant in participants %}{{ participant.name }} : {{ participant.nb_places }} place{{ participant.nb_places|pluralize }}
{% endfor %}
{% endspaceless %}</pre>
</div>
<div>
<a href="{% url 'bda-rappels' spectacle.id %}">Page d'envoi manuel des mails de rappel</a>
</div>
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 %}
{% 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 "calendar" %}">calendrier
automatique&#8239;!</a></p>
{% else %}
<h3>Vous n'avez aucune place :(</h3>
{% endif %}
{% endblock %}

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,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,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,121 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>Gestion des places que je revends</h2>
{% if resell_exists %}
<br />
<h3>Places non revendues</h3>
<form class="form-horizontal" action="" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Cochez les places que vous souhaitez revendre, et validez. Vous aurez
ensuite 1h pour changer d'avis avant que la revente soit confirmée et
que les notifications soient envoyées aux intéressé·e·s.
</div>
{% csrf_token %}
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Date</th>
<th data-sort="int">Prix</th>
</tr>
</thead>
<tbody>
{% for checkbox in resellform.attributions %}{{ checkbox }}{% endfor %}
</tbody>
</table>
<div class="form-actions">
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
</div>
</form>
<hr />
{% endif %}
{% if annul_exists %}
<h3>Places en cours de revente</h3>
<form action="" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Vous pouvez annuler les reventes qui n'ont pas encore trouvé preneur·se.
</div>
{% csrf_token %}
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
<th data-sort="int">Tirage le</th>
</tr>
</thead>
<tbody>
{% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %}
</tbody>
</table>
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
</form>
<hr />
{% endif %}
{% if sold_exists %}
<h3>Places revendues</h3>
<form action="" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Pour chaque revente, vous devez soit l'annuler soit la confirmer pour
transférer la place la place à la personne tirée au sort.
L'annulation sert par exemple à pouvoir remettre la place en jeu si
vous ne parvenez pas à entrer en contact avec la personne tirée au
sort.
</div>
{% csrf_token %}
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
<th>Vendue à</th>
</tr>
</thead>
<tbody>
{% for checkbox in soldform.reventes %}{{ checkbox }}{% endfor %}
</tbody>
</table>
<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 resell_exists and not annul_exists and not sold_exists %}
<p>Plus de reventes possibles !</p>
{% endif %}
<script language="JavaScript">
$(function(){
$("table.stupidtable").stupidtable();
});
$("tr").click(function() {
$(this).find("input[type=checkbox]").click()
});
$("input[type=checkbox]").click(function(e) {
e.stopPropagation();
});
</script>
{% 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,29 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Places disponibles immédiatement</h2>
{% if spectacles %}
<table class="table table-striped stupidtable" id="bda-shotgun">
<thead>
<tr>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
<th></th>
</tr>
</thead>
<tbody>
{% for spectacle in spectacles %}
<tr>
{% include "bda/forms/spectacle_label_table.html" with spectacle=spectacle %}
<td class="button"><a role="button" class="btn btn-primary" href="{% url 'bda-revente-buy' spectacle.id %}">Racheter</a>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> Pas de places disponibles immédiatement, désolé !</p>
{% endif %}
{% endblock %}

View file

@ -1,64 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles%}
{% block realcontent %}
<h2>Inscriptions pour BdA-Revente</h2>
<form action="" class="form-horizontal" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Cochez les spectacles pour lesquels vous souhaitez recevoir une
notification quand une place est disponible en revente. <br />
Lorsque vous validez vos choix, si un tirage au sort est en cours pour
un des spectacles que vous avez sélectionné, vous serez automatiquement
inscrit à ce tirage.
</div>
<br />
{% csrf_token %}
<div class="form-group">
<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>
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
</tr>
</thead>
<tbody>
{% for checkbox in form.spectacles %}{{ checkbox }}{% endfor %}
</tbody>
</table>
</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;
}
}
$(function(){
$("table.stupidtable").stupidtable();
});
$("tr").click(function() {
$(this).find("input[type=checkbox]").click()
});
$("input[type=checkbox]").click(function(e) {
e.stopPropagation();
});
</script>
{% endblock %}

View file

@ -1,99 +0,0 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% block realcontent %}
<h2>Tirages au sort de reventes</h2>
{% if annul_exists %}
<h3>Les reventes auxquelles vous êtes inscrit·e</h3>
<form class="form-horizontal" action="" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Voici la liste des reventes auxquelles vous êtes inscrit·e ; si vous ne souhaitez plus participer au tirage au sort vous pouvez vous en désister.
</div>
{% csrf_token %}
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
<th>Vendue par</th>
<th data-sort="int">Tirage le</th>
</tr>
</thead>
<tbody>
{% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %}
</tbody>
</table>
<div class="form-actions">
<input type="submit"
class="btn btn-primary"
name="annul"
value="Se désister des tirages sélectionnés">
</div>
</form>
{% endif %}
<hr />
{% if sub_exists %}
<h3>Tirages en cours</h3>
<form class="form-horizontal" action="" method="post">
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Vous pouvez vous inscrire aux tirages en cours suivants.
</div>
{% csrf_token %}
<table class="table table-striped stupidtable">
<thead>
<tr>
<th></th>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Prix</th>
<th>Vendue par</th>
<th data-sort="int">Tirage le</th>
</tr>
</thead>
<tbody>
{% for checkbox in subform.reventes %}{{ checkbox }}{% endfor %}
</tbody>
</table>
<div class="form-actions">
<input type="submit"
class="btn btn-primary"
name="subscribe"
value="S'inscrire aux tirages sélectionnés">
</div>
</form>
{% endif %}
{% if not annul_exists and not sub_exists %}
<div class="bg-info text-info center-block">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Aucune revente n'est active pour le moment !
</div>
{% endif %}
<script language="JavaScript">
$(function(){
$("table.stupidtable").stupidtable();
});
$("tr").click(function() {
$(this).find("input[type=checkbox]").click()
});
$("input[type=checkbox]").click(function(e) {
e.stopPropagation();
});
</script>
{% 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-revente-buy" 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 extra_head %}
<link type="text/css" rel="stylesheet" href="{% static "bda/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">
$(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>
</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

View file

@ -1,102 +0,0 @@
from datetime import timedelta
from unittest import mock
from django.contrib.auth import get_user_model
from django.core import mail
from django.test import TestCase
from django.utils import timezone
from bda.models import (
Attribution,
Participant,
Salle,
Spectacle,
SpectacleRevente,
Tirage,
)
User = get_user_model()
class SpectacleReventeTests(TestCase):
fixtures = ["gestioncof/management/data/custommail.json"]
def setUp(self):
now = timezone.now()
self.t = Tirage.objects.create(
title="Tirage",
ouverture=now - timedelta(days=7),
fermeture=now - timedelta(days=3),
active=True,
)
self.s = Spectacle.objects.create(
title="Spectacle",
date=now + timedelta(days=20),
location=Salle.objects.create(name="Salle", address="Address"),
price=10.5,
slots=5,
tirage=self.t,
listing=False,
)
self.seller = Participant.objects.create(
user=User.objects.create(username="seller", email="seller@mail.net"),
tirage=self.t,
)
self.p1 = Participant.objects.create(
user=User.objects.create(username="part1", email="part1@mail.net"),
tirage=self.t,
)
self.p2 = Participant.objects.create(
user=User.objects.create(username="part2", email="part2@mail.net"),
tirage=self.t,
)
self.p3 = Participant.objects.create(
user=User.objects.create(username="part3", email="part3@mail.net"),
tirage=self.t,
)
self.attr = Attribution.objects.create(
participant=self.seller, spectacle=self.s
)
self.rev = SpectacleRevente.objects.create(
attribution=self.attr, seller=self.seller
)
def test_tirage(self):
revente = self.rev
wanted_by = [self.p1, self.p2, self.p3]
revente.confirmed_entry.set(wanted_by)
with mock.patch("bda.models.random.choice") as mc:
# Set winner to self.p1.
mc.return_value = self.p1
revente.tirage()
# Call to random.choice used participants in wanted_by.
mc_args, _ = mc.call_args
self.assertEqual(set(mc_args[0]), set(wanted_by))
self.assertEqual(revente.soldTo, self.p1)
self.assertTrue(revente.tirage_done)
mails = {m.to[0]: m for m in mail.outbox}
self.assertEqual(len(mails), 4)
m_seller = mails["seller@mail.net"]
self.assertListEqual(m_seller.to, ["seller@mail.net"])
self.assertListEqual(m_seller.reply_to, ["part1@mail.net"])
m_winner = mails["part1@mail.net"]
self.assertListEqual(m_winner.to, ["part1@mail.net"])
self.assertCountEqual(
[mails["part2@mail.net"].to, mails["part3@mail.net"].to],
[["part2@mail.net"], ["part3@mail.net"]],
)

View file

@ -1,79 +0,0 @@
from datetime import timedelta
from django.contrib.auth.models import User
from django.test import TestCase
from django.utils import timezone
from bda.models import (
Attribution,
CategorieSpectacle,
Participant,
Salle,
Spectacle,
SpectacleRevente,
Tirage,
)
class TestModels(TestCase):
def setUp(self):
self.tirage = Tirage.objects.create(
title="Tirage test",
appear_catalogue=True,
ouverture=timezone.now(),
fermeture=timezone.now(),
)
self.category = CategorieSpectacle.objects.create(name="Category")
self.location = Salle.objects.create(name="here")
self.spectacle_soon = Spectacle.objects.create(
title="foo",
date=timezone.now() + timedelta(days=1),
location=self.location,
price=0,
slots=42,
tirage=self.tirage,
listing=False,
category=self.category,
)
self.spectacle_later = Spectacle.objects.create(
title="bar",
date=timezone.now() + timedelta(days=30),
location=self.location,
price=0,
slots=42,
tirage=self.tirage,
listing=False,
category=self.category,
)
user_buyer = User.objects.create_user(
username="bda_buyer", password="testbuyer"
)
user_seller = User.objects.create_user(
username="bda_seller", password="testseller"
)
self.buyer = Participant.objects.create(user=user_buyer, tirage=self.tirage)
self.seller = Participant.objects.create(user=user_seller, tirage=self.tirage)
self.attr_soon = Attribution.objects.create(
participant=self.seller, spectacle=self.spectacle_soon
)
self.attr_later = Attribution.objects.create(
participant=self.seller, spectacle=self.spectacle_later
)
self.revente_soon = SpectacleRevente.objects.create(
seller=self.seller, attribution=self.attr_soon
)
self.revente_later = SpectacleRevente.objects.create(
seller=self.seller, attribution=self.attr_later
)
def test_urgent(self):
self.assertTrue(self.revente_soon.is_urgent)
self.assertFalse(self.revente_later.is_urgent)
def test_tirage(self):
self.revente_soon.confirmed_entry.add(self.buyer)
self.assertEqual(self.revente_soon.tirage(send_mails=False), self.buyer)
self.assertIsNone(self.revente_later.tirage(send_mails=False))

View file

@ -1,365 +0,0 @@
import json
from datetime import timedelta
from unittest import mock
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import formats, timezone
from ..models import Participant, Tirage
from .testcases import BdATestHelpers, BdAViewTestCaseMixin
User = get_user_model()
class InscriptionViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-tirage-inscription"
http_methods = ["GET", "POST"]
auth_user = "bda_member"
auth_forbidden = [None, "bda_other"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/inscription/{}".format(self.tirage.id)
def test_get_opened(self):
self.tirage.ouverture = timezone.now() - timedelta(days=1)
self.tirage.fermeture = timezone.now() + timedelta(days=1)
self.tirage.save()
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 200)
self.assertFalse(resp.context["messages"])
def test_get_closed_future(self):
self.tirage.ouverture = timezone.now() + timedelta(days=1)
self.tirage.fermeture = timezone.now() + timedelta(days=2)
self.tirage.save()
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 200)
self.assertIn(
"Le tirage n'est pas encore ouvert : ouverture le {}".format(
formats.localize(timezone.template_localtime(self.tirage.ouverture))
),
[str(msg) for msg in resp.context["messages"]],
)
def test_get_closed_past(self):
self.tirage.ouverture = timezone.now() - timedelta(days=2)
self.tirage.fermeture = timezone.now() - timedelta(days=1)
self.tirage.save()
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 200)
self.assertIn(
" C'est fini : tirage au sort dans la journée !",
[str(msg) for msg in resp.context["messages"]],
)
def get_base_post_data(self):
return {
"choixspectacle_set-TOTAL_FORMS": "3",
"choixspectacle_set-INITIAL_FORMS": "0",
"choixspectacle_set-MIN_NUM_FORMS": "0",
"choixspectacle_set-MAX_NUM_FORMS": "1000",
}
base_post_data = property(get_base_post_data)
def test_post(self):
self.tirage.ouverture = timezone.now() - timedelta(days=1)
self.tirage.fermeture = timezone.now() + timedelta(days=1)
self.tirage.save()
data = dict(
self.base_post_data,
**{
"choixspectacle_set-TOTAL_FORMS": "2",
"choixspectacle_set-0-id": "",
"choixspectacle_set-0-participant": "",
"choixspectacle_set-0-spectacle": str(self.show1.pk),
"choixspectacle_set-0-double_choice": "1",
"choixspectacle_set-0-priority": "2",
"choixspectacle_set-1-id": "",
"choixspectacle_set-1-participant": "",
"choixspectacle_set-1-spectacle": str(self.show2.pk),
"choixspectacle_set-1-double_choice": "autoquit",
"choixspectacle_set-1-priority": "1",
}
)
resp = self.client.post(self.url, data)
self.assertEqual(resp.status_code, 200)
self.assertIn(
"Votre inscription a été mise à jour avec succès !",
[str(msg) for msg in resp.context["messages"]],
)
participant = Participant.objects.get(
user=self.users["bda_member"], tirage=self.tirage
)
self.assertSetEqual(
set(
participant.choixspectacle_set.values_list(
"priority", "spectacle_id", "double_choice"
)
),
{(1, self.show2.pk, "autoquit"), (2, self.show1.pk, "1")},
)
def test_post_state_changed(self):
self.tirage.ouverture = timezone.now() - timedelta(days=1)
self.tirage.fermeture = timezone.now() + timedelta(days=1)
self.tirage.save()
data = {"dbstate": "different"}
resp = self.client.post(self.url, data)
self.assertEqual(resp.status_code, 200)
self.assertIn(
"Impossible d'enregistrer vos modifications : vous avez apporté d'autres "
"modifications entre temps.",
[str(msg) for msg in resp.context["messages"]],
)
class PlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-places-attribuees"
auth_user = "bda_member"
auth_forbidden = [None, "bda_other"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/places/{}".format(self.tirage.id)
class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-etat-places"
auth_user = "bda_member"
auth_forbidden = [None, "bda_other"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/etat-places/{}".format(self.tirage.id)
class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-tirage"
http_methods = ["GET", "POST"]
auth_user = "bda_staff"
auth_forbidden = [None, "bda_other", "bda_member"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/tirage/{}".format(self.tirage.id)
def test_perform_tirage_disabled(self):
# Cannot be performed if disabled
self.tirage.enable_do_tirage = False
self.tirage.save()
resp = self.client.get(self.url)
self.assertTemplateUsed(resp, "tirage-failed.html")
def test_perform_tirage_opened_registrations(self):
# Cannot be performed if registrations are still open
self.tirage.enable_do_tirage = True
self.tirage.fermeture = timezone.now() + timedelta(seconds=3600)
self.tirage.save()
resp = self.client.get(self.url)
self.assertTemplateUsed(resp, "tirage-failed.html")
def test_perform_tirage(self):
# Otherwise, perform the tirage
self.tirage.enable_do_tirage = True
self.tirage.fermeture = timezone.now()
self.tirage.save()
resp = self.client.get(self.url)
self.assertTemplateNotUsed(resp, "tirage-failed.html")
class SpectacleListViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-liste-spectacles"
auth_user = "bda_staff"
auth_forbidden = [None, "bda_other", "bda_member"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/spectacles/{}".format(self.tirage.id)
class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-spectacle"
auth_user = "bda_staff"
auth_forbidden = [None, "bda_other", "bda_member"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id, "spectacle_id": self.show1.id}
@property
def url_expected(self):
return "/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id)
class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-unpaid"
auth_user = "bda_staff"
auth_forbidden = [None, "bda_other", "bda_member"]
bda_testdata = True
@property
def url_kwargs(self):
return {"tirage_id": self.tirage.id}
@property
def url_expected(self):
return "/bda/spectacles/unpaid/{}".format(self.tirage.id)
class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
url_name = "bda-rappels"
auth_user = "bda_staff"
auth_forbidden = [None, "bda_other", "bda_member"]
bda_testdata = True
@property
def url_kwargs(self):
return {"spectacle_id": self.show1.id}
@property
def url_expected(self):
return "/bda/mails-rappel/{}".format(self.show1.id)
def test_post(self):
self.require_custommails()
resp = self.client.post(self.url)
self.assertEqual(200, resp.status_code)
# TODO: check that emails are sent
class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
auth_user = None
auth_forbidden = []
bda_testdata = True
def test_api_list(self):
url_list = "/bda/catalogue/list"
resp = self.client.get(url_list)
self.assertJSONEqual(
resp.content.decode("utf-8"),
[{"id": self.tirage.id, "title": self.tirage.title}],
)
def test_api_details(self):
url_details = "/bda/catalogue/details?id={}".format(self.tirage.id)
resp = self.client.get(url_details)
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}],
},
)
def test_api_descriptions(self):
url_descriptions = "/bda/catalogue/descriptions?id={}".format(self.tirage.id)
resp = self.client.get(url_descriptions)
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)},
)
# ----- BdA Revente --------------------------------------- #
def make_participant(name: str, tirage: Tirage) -> User:
user = User.objects.create_user(username=name, password=name)
user.profile.is_cof = True
user.profile.save()
Participant.objects.create(user=user, tirage=tirage)
return user
class TestReventeManageTest(TestCase):
def setUp(self):
self.tirage = Tirage.objects.create(
title="tirage1",
ouverture=timezone.now(),
fermeture=timezone.now() + timedelta(days=90),
)
self.user = make_participant("toto", self.tirage)
self.url = reverse("bda-revente-manage", args=[self.tirage.id])
# Signals handlers on login/logout send messages.
# Due to the way the Django' test Client performs login, this raise an
# error. As workaround, we mock the Django' messages module.
patcher_messages = mock.patch("gestioncof.signals.messages")
patcher_messages.start()
self.addCleanup(patcher_messages.stop)
def test_can_get(self):
client = Client()
client.force_login(self.user)
r = client.get(self.url)
self.assertEqual(r.status_code, 200)
class TestBdaRevente:
pass
# TODO

View file

@ -1,75 +0,0 @@
import os
from django.conf import settings
from django.core.management import call_command
from django.utils import timezone
from shared.tests.testcases import ViewTestCaseMixin
from ..models import CategorieSpectacle, Salle, Spectacle, Tirage
from .utils import create_user
class BdAViewTestCaseMixin(ViewTestCaseMixin):
def get_users_base(self):
return {
"bda_other": create_user(username="bda_other"),
"bda_member": create_user(username="bda_member", is_cof=True),
"bda_staff": create_user(username="bda_staff", is_cof=True, is_buro=True),
}
class BdATestHelpers:
bda_testdata = False
def setUp(self):
super().setUp()
if self.bda_testdata:
self.load_bda_testdata()
def require_custommails(self):
data_file = os.path.join(
settings.BASE_DIR, "gestioncof", "management", "data", "custommail.json"
)
call_command("syncmails", data_file, verbosity=0)
def load_bda_testdata(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")
self.show1 = Spectacle.objects.create(
title="foo",
date=timezone.now(),
location=self.location,
price=0,
slots=42,
tirage=self.tirage,
listing=False,
category=self.category,
)
self.show2 = Spectacle.objects.create(
title="bar",
date=timezone.now(),
location=self.location,
price=1,
slots=142,
tirage=self.tirage,
listing=False,
category=self.category,
)
self.show3 = Spectacle.objects.create(
title="baz",
date=timezone.now(),
location=self.location,
price=2,
slots=242,
tirage=self.tirage,
listing=False,
category=self.category,
)

View file

@ -1,36 +0,0 @@
from datetime import timedelta
from django.contrib.auth.models import User
from django.utils import timezone
from ..models import CategorieSpectacle, Salle, Spectacle, Tirage
def create_user(username, is_cof=False, is_buro=False):
user = User.objects.create_user(username=username, password=username)
user.profile.is_cof = is_cof
user.profile.is_buro = is_buro
user.profile.save()
return user
def user_is_cof(user):
return (user is not None) and user.profile.is_cof
def user_is_staff(user):
return (user is not None) and user.profile.is_buro
def create_spectacle(**kwargs):
defaults = {
"title": "Title",
"category": CategorieSpectacle.objects.first(),
"date": (timezone.now() + timedelta(days=7)).date(),
"location": Salle.objects.first(),
"price": 10.0,
"slots": 20,
"tirage": Tirage.objects.first(),
"listing": False,
}
return Spectacle.objects.create(**dict(defaults, **kwargs))

View file

@ -1,74 +0,0 @@
from django.conf.urls import url
from bda import views
from bda.views import SpectacleListView
from gestioncof.decorators import buro_required
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"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-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.UnpaidParticipants.as_view(),
name="bda-unpaid",
),
url(
r"^spectacles/autocomplete$",
views.spectacle_autocomplete,
name="bda-spectacle-autocomplete",
),
url(
r"^participants/autocomplete$",
views.participant_autocomplete,
name="bda-participant-autocomplete",
),
# Urls BdA-Revente
url(
r"^revente/(?P<tirage_id>\d+)/manage$",
views.revente_manage,
name="bda-revente-manage",
),
url(
r"^revente/(?P<tirage_id>\d+)/subscribe$",
views.revente_subscribe,
name="bda-revente-subscribe",
),
url(
r"^revente/(?P<tirage_id>\d+)/tirages$",
views.revente_tirages,
name="bda-revente-tirages",
),
url(
r"^revente/(?P<spectacle_id>\d+)/buy$",
views.revente_buy,
name="bda-revente-buy",
),
url(
r"^revente/(?P<revente_id>\d+)/confirm$",
views.revente_confirm,
name="bda-revente-confirm",
),
url(
r"^revente/(?P<tirage_id>\d+)/shotgun$",
views.revente_shotgun,
name="bda-revente-shotgun",
),
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
]

View file

@ -1,907 +0,0 @@
import hashlib
import json
import random
import time
from collections import defaultdict
from custommail.models import CustomMail
from custommail.shortcuts import send_custom_mail, send_mass_custom_mail
from django.conf import settings
from django.contrib import messages
from django.core import serializers
from django.core.exceptions import NON_FIELD_ERRORS
from django.db import transaction
from django.db.models import Count, Prefetch
from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.template.defaultfilters import pluralize
from django.urls import reverse
from django.utils import formats, timezone
from django.views.generic.list import ListView
from bda.algorithm import Algorithm
from bda.forms import (
AnnulForm,
InscriptionInlineFormSet,
InscriptionReventeForm,
ResellForm,
ReventeTirageAnnulForm,
ReventeTirageForm,
SoldForm,
TokenForm,
)
from bda.models import (
Attribution,
CategorieSpectacle,
ChoixSpectacle,
Participant,
Salle,
Spectacle,
SpectacleRevente,
Tirage,
)
from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required
from utils.views.autocomplete import Select2QuerySetView
@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)
spectacles = tirage.spectacle_set.select_related("location")
spectacles_dict = {} # index of spectacle by id
for spectacle in spectacles:
spectacle.total = 0 # init total requests
spectacles_dict[spectacle.id] = spectacle
choices = (
ChoixSpectacle.objects.filter(spectacle__in=spectacles)
.values("spectacle")
.annotate(total=Count("spectacle"))
)
# choices *by spectacles* whose only 1 place is requested
choices1 = choices.filter(double_choice="1")
# choices *by spectacles* whose 2 places is requested
choices2 = choices.exclude(double_choice="1")
for spectacle in choices1:
pk = spectacle["spectacle"]
spectacles_dict[pk].total += spectacle["total"]
for spectacle in choices2:
pk = spectacle["spectacle"]
spectacles_dict[pk].total += 2 * spectacle["total"]
# here, each spectacle.total contains the number of requests
slots = 0 # proposed slots
total = 0 # requests
for spectacle in spectacles:
slots += spectacle.slots
total += spectacle.total
spectacle.ratio = spectacle.total / spectacle.slots
context = {
"proposed": slots,
"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, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
places = participant.attribution_set.order_by(
"spectacle__date", "spectacle"
).select_related("spectacle", "spectacle__location")
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", {})
participant, _ = Participant.objects.select_related("tirage").get_or_create(
user=request.user, tirage=tirage
)
if timezone.now() > tirage.fermeture:
# Le tirage est fermé.
choices = participant.choixspectacle_set.order_by("priority")
messages.error(request, " C'est fini : tirage au sort dans la journée !")
return render(
request, "bda/resume-inscription-tirage.html", {"choices": choices}
)
BdaFormSet = inlineformset_factory(
Participant,
ChoixSpectacle,
fields=("spectacle", "double_choice", "priority"),
formset=InscriptionInlineFormSet,
error_messages={
NON_FIELD_ERRORS: {
"unique_together": "Vous avez déjà demandé ce voeu plus haut !"
}
},
)
if request.method == "POST":
# use *this* queryset
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
formset = BdaFormSet(instance=participant)
messages.error(
request,
"Impossible d'enregistrer vos modifications "
": vous avez apporté d'autres modifications "
"entre temps.",
)
else:
formset = BdaFormSet(request.POST, instance=participant)
if formset.is_valid():
formset.save()
formset = BdaFormSet(instance=participant)
participant.accepte_charte = True
participant.save()
messages.success(
request, "Votre inscription a été mise à jour avec succès !"
)
else:
messages.error(
request,
"Une erreur s'est produite lors de l'enregistrement de vos vœux. "
"Avez-vous demandé plusieurs fois le même spectacle ?",
)
else:
formset = BdaFormSet(instance=participant)
# use *this* queryset
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
choices = participant.choixspectacle_set.select_related("spectacle")
for choice in choices:
total_price += choice.spectacle.price
if choice.double:
total_price += choice.spectacle.price
return render(
request,
"bda/inscription-tirage.html",
{
"formset": formset,
"total_price": total_price,
"dbstate": dbstate,
"tirage": tirage,
"charte": participant.accepte_charte,
},
)
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("location"),
"token": token,
"members": tirage_elt.participant_set.select_related("user"),
"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("participant", "participant__user", "spectacle")
)
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
ChoixRevente = Participant.choicesrevente.through
# Suppression des reventes demandées/enregistrées
# (si le tirage est relancé)
(ChoixRevente.objects.filter(spectacle__tirage=tirage_elt).delete())
(
SpectacleRevente.objects.filter(
attribution__spectacle__tirage=tirage_elt
).delete()
)
lost_by = defaultdict(set)
for show, _, losers in results:
for loser, _, _, _ in losers:
lost_by[loser].add(show)
ChoixRevente.objects.bulk_create(
ChoixRevente(participant=member, spectacle=show)
for member, shows in lost_by.items()
for show in shows
)
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})
@cof_required
def revente_manage(request, tirage_id):
"""
Gestion de ses propres reventes :
- Création d'une revente
- Annulation d'une revente
- Confirmation d'une revente = transfert de la place à la personne qui
rachète
- Annulation d'une revente après que le tirage a eu lieu
"""
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.annotate_paid().get_or_create(
user=request.user, tirage=tirage
)
if not participant.paid:
return render(request, "bda/revente/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.reset()
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():
reventes = annulform.cleaned_data["reventes"]
for revente in reventes:
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():
reventes = soldform.cleaned_data["reventes"]
for revente in reventes:
revente.attribution.participant = revente.soldTo
revente.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():
reventes = soldform.cleaned_data["reventes"]
for revente in reventes:
if revente.attribution.spectacle.date > timezone.now():
# On antidate pour envoyer le mail plus vite
new_date = timezone.now() - SpectacleRevente.remorse_time
revente.reset(new_date=new_date)
sold_exists = soldform.fields["reventes"].queryset.exists()
annul_exists = annulform.fields["reventes"].queryset.exists()
resell_exists = resellform.fields["attributions"].queryset.exists()
return render(
request,
"bda/revente/manage.html",
{
"tirage": tirage,
"soldform": soldform,
"annulform": annulform,
"resellform": resellform,
"sold_exists": sold_exists,
"annul_exists": annul_exists,
"resell_exists": resell_exists,
},
)
@cof_required
def revente_tirages(request, tirage_id):
"""
Affiche à un participant la liste de toutes les reventes en cours (pour un
tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
"""
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
subform = ReventeTirageForm(participant, prefix="subscribe")
annulform = ReventeTirageAnnulForm(participant, prefix="annul")
if request.method == "POST":
if "subscribe" in request.POST:
subform = ReventeTirageForm(participant, request.POST, prefix="subscribe")
if subform.is_valid():
reventes = subform.cleaned_data["reventes"]
count = reventes.count()
for revente in reventes:
revente.confirmed_entry.add(participant)
if count > 0:
messages.success(
request,
"Tu as bien été inscrit à {} revente{}".format(
count, pluralize(count)
),
)
elif "annul" in request.POST:
annulform = ReventeTirageAnnulForm(
participant, request.POST, prefix="annul"
)
if annulform.is_valid():
reventes = annulform.cleaned_data["reventes"]
count = reventes.count()
for revente in reventes:
revente.confirmed_entry.remove(participant)
if count > 0:
messages.success(
request,
"Tu as bien été désinscrit de {} revente{}".format(
count, pluralize(count)
),
)
annul_exists = annulform.fields["reventes"].queryset.exists()
sub_exists = subform.fields["reventes"].queryset.exists()
return render(
request,
"bda/revente/tirages.html",
{
"annulform": annulform,
"subform": subform,
"annul_exists": annul_exists,
"sub_exists": sub_exists,
},
)
@cof_required
def revente_confirm(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 not revente.notif_sent or revente.shotgun:
return render(request, "bda/revente/wrongtime.html", {"revente": revente})
revente.confirmed_entry.add(participant)
return render(
request,
"bda/revente/confirmed.html",
{"spectacle": revente.attribution.spectacle, "date": revente.date_tirage},
)
@cof_required
def revente_subscribe(request, tirage_id):
"""
Permet à un participant de sélectionner ses préférences pour les reventes.
Il recevra des notifications pour les spectacles qui l'intéressent et il
est automatiquement inscrit aux reventes en cours au moment il ajoute un
spectacle à la liste des spectacles qui l'intéressent.
"""
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.set(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("confirmed_entry"))
.order_by("nb_subscribers")
.first()
)
if min_resell is not None:
min_resell.confirmed_entry.add(participant)
inscrit_revente.append(spectacle)
success = True
else:
form = InscriptionReventeForm(
tirage, initial={"spectacles": participant.choicesrevente.all()}
)
# Messages
if success:
messages.success(request, "Votre inscription a bien été prise en compte")
if deja_revente:
messages.info(
request,
"Des reventes existent déjà pour certains de "
"ces spectacles, vérifiez les places "
"disponibles sans tirage !",
)
if inscrit_revente:
shows = map("<li>{!s}</li>".format, inscrit_revente)
msg = (
"Vous avez été inscrit·e à 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/revente/subscribe.html", {"form": form})
@cof_required
def revente_buy(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.
own_reventes = reventes.filter(seller=participant)
if len(own_reventes) > 0:
own_reventes[0].delete()
return HttpResponseRedirect(reverse("bda-revente-shotgun", args=[tirage.id]))
reventes_shotgun = reventes.filter(shotgun=True)
if not reventes_shotgun:
return render(request, "bda/revente/none.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/revente/mail-success.html",
{"seller": revente.attribution.participant.user, "spectacle": spectacle},
)
return render(
request,
"bda/revente/confirm-shotgun.html",
{"spectacle": spectacle, "user": request.user},
)
@cof_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())
.select_related("location")
.prefetch_related(
Prefetch(
"attribues",
queryset=(
Attribution.objects.filter(
revente__shotgun=True, revente__soldTo__isnull=True
)
),
to_attr="shotguns",
)
)
)
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
return render(request, "bda/revente/shotgun.html", {"spectacles": 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.select_related(
"participant", "participant__user"
)
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": True,
"nb_places": 1,
}
if participant.id in participants:
participants[participant.id]["nb_places"] += 1
participants[participant.id]["given"] += attrib.given
participants[participant.id]["paid"] &= attrib.paid
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.select_related("location")
return categories
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tirage_id"] = self.tirage.id
context["tirage_name"] = self.tirage.title
return context
class UnpaidParticipants(BuroRequiredMixin, ListView):
context_object_name = "unpaid"
template_name = "bda-unpaid.html"
def get_queryset(self):
return (
Participant.objects.annotate_paid()
.filter(tirage__id=self.kwargs["tirage_id"], paid=False)
.select_related("user")
)
@buro_required
def send_rappel(request, spectacle_id):
show = get_object_or_404(Spectacle, id=spectacle_id)
# Mails d'exemples
custommail = CustomMail.objects.get(shortname="bda-rappel")
exemple_mail_1place = custommail.render(
{"member": request.user, "show": show, "nb_attr": 1}
)
exemple_mail_2places = custommail.render(
{"member": request.user, "show": show, "nb_attr": 2}
)
# Contexte
ctxt = {
"show": show,
"exemple_mail_1place": exemple_mail_1place,
"exemple_mail_2places": exemple_mail_2places,
"custommail": custommail,
}
# Envoi confirmé
if request.method == "POST":
members = show.send_rappel()
ctxt["sent"] = True
ctxt["members"] = members
# Demande de confirmation
else:
ctxt["sent"] = False
if show.rappel_sent:
messages.warning(
request,
"Attention, un mail de rappel pour ce spectale a déjà été "
"envoyé le {}".format(
formats.localize(timezone.template_localtime(show.rappel_sent))
),
)
return render(request, "bda/mails-rappel.html", ctxt)
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.select_related("location").prefetch_related(
"quote_set"
)
if categories_id and 0 not in categories_id:
shows_qs = shows_qs.filter(category__id__in=categories_id)
if locations_id and 0 not in 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": [
dict(author=quote.author, text=quote.text)
for quote in spectacle.quote_set.all()
],
"image": spectacle.getImgUrl(),
"ext_link": spectacle.ext_link,
"price": spectacle.price,
"slots": spectacle.slots,
}
for spectacle in shows_qs
]
return JsonResponse(data_return, safe=False)
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
return HttpResponseBadRequest()
##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##
class ParticipantAutocomplete(Select2QuerySetView):
model = Participant
search_fields = ("user__username", "user__first_name", "user__last_name")
participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())
class SpectacleAutocomplete(Select2QuerySetView):
model = Spectacle
search_fields = ("title",)
spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())

View file

View file

@ -1,5 +0,0 @@
from django.contrib import admin
from bds.models import BDSProfile
admin.site.register(BDSProfile)

View file

@ -1,5 +0,0 @@
from django.apps import AppConfig
class BdsConfig(AppConfig):
name = "bds"

View file

@ -1,142 +0,0 @@
# Generated by Django 2.2 on 2019-07-17 12:48
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import bds.models
class Migration(migrations.Migration):
initial = True
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
operations = [
migrations.CreateModel(
name="BDSProfile",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"phone",
models.CharField(
blank=True, max_length=20, verbose_name="téléphone"
),
),
(
"occupation",
models.CharField(
choices=[
("EXT", "Extérieur"),
("1A", "1A"),
("2A", "2A"),
("3A", "3A"),
("4A", "4A"),
("MAG", "Magistérien"),
("ARC", "Archicube"),
("DOC", "Doctorant"),
("CST", "CST"),
("PER", "Personnel ENS"),
],
default="1A",
max_length=3,
verbose_name="occupation",
),
),
(
"departement",
models.CharField(
blank=True, max_length=50, verbose_name="département"
),
),
(
"birthdate",
models.DateField(
blank=True, null=True, verbose_name="date de naissance"
),
),
(
"mails_bds",
models.BooleanField(
default=False, verbose_name="recevoir les mails du BDS"
),
),
(
"is_buro",
models.BooleanField(
default=False, verbose_name="membre du Burô du BDS"
),
),
(
"has_certificate",
models.BooleanField(
default=False, verbose_name="certificat médical"
),
),
(
"certificate_file",
models.FileField(
blank=True,
upload_to=bds.models.BDSProfile.get_certificate_filename,
verbose_name="fichier de certificat médical",
),
),
(
"ASPSL_number",
models.CharField(
blank=True,
max_length=50,
null=True,
verbose_name="numéro AS PSL",
),
),
(
"FFSU_number",
models.CharField(
blank=True, max_length=50, null=True, verbose_name="numéro FFSU"
),
),
(
"cotisation_period",
models.CharField(
choices=[
("ANN", "Année"),
("SE1", "Premier semestre"),
("SE2", "Deuxième semestre"),
("NO", "Aucune"),
],
default="NO",
max_length=3,
verbose_name="inscription",
),
),
(
"registration_date",
models.DateField(
auto_now_add=True, verbose_name="date d'inscription"
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="bds",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Profil BDS",
"verbose_name_plural": "Profils BDS",
},
)
]

View file

@ -1,30 +0,0 @@
# Generated by Django 2.2 on 2019-07-17 14:56
from django.contrib.auth.management import create_permissions
from django.db import migrations
from django.db.models import Q
def create_bds_buro_group(apps, schema_editor):
for app_config in apps.get_app_configs():
create_permissions(app_config, apps=apps, verbosity=0)
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
group, created = Group.objects.get_or_create(name="Burô du BDS")
if created:
perms = Permission.objects.filter(
Q(content_type__app_label="bds")
| Q(content_type__app_label="auth") & Q(content_type__model="user")
)
group.permissions.set(perms)
group.save()
class Migration(migrations.Migration):
dependencies = [("bds", "0001_initial")]
operations = [
migrations.RunPython(create_bds_buro_group, migrations.RunPython.noop)
]

View file

@ -1,95 +0,0 @@
from datetime import date
from os.path import splitext
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
from shared.utils import choices_length
User = get_user_model()
class BDSProfile(models.Model):
OCCUPATION_CHOICES = (
("EXT", "Extérieur"),
("1A", "1A"),
("2A", "2A"),
("3A", "3A"),
("4A", "4A"),
("MAG", "Magistérien"),
("ARC", "Archicube"),
("DOC", "Doctorant"),
("CST", "CST"),
("PER", "Personnel ENS"),
)
TYPE_COTIZ_CHOICES = (
("ETU", "Étudiant"),
("NOR", "Normalien"),
("EXT", "Extérieur"),
("ARC", "Archicube"),
)
COTIZ_DURATION_CHOICES = (
("ANN", "Année"),
("SE1", "Premier semestre"),
("SE2", "Deuxième semestre"),
("NO", "Aucune"),
)
def get_certificate_filename(instance, filename):
_, ext = splitext(filename) # récupère l'extension du fichier
year = str(date.now().year)
return "certifs/{username}-{year}.{ext}".format(
username=instance.user.username, year=year, ext=ext
)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="bds")
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)
birthdate = models.DateField(
auto_now_add=False,
auto_now=False,
verbose_name=_("date de naissance"),
blank=True,
null=True,
)
mails_bds = models.BooleanField(_("recevoir les mails du BDS"), default=False)
is_buro = models.BooleanField(_("membre du Burô du BDS"), default=False)
has_certificate = models.BooleanField(_("certificat médical"), default=False)
certificate_file = models.FileField(
_("fichier de certificat médical"),
upload_to=get_certificate_filename,
blank=True,
)
ASPSL_number = models.CharField(
_("numéro AS PSL"), max_length=50, blank=True, null=True
)
FFSU_number = models.CharField(
_("numéro FFSU"), max_length=50, blank=True, null=True
)
cotisation_period = models.CharField(
_("inscription"), default="NO", choices=COTIZ_DURATION_CHOICES, max_length=3
)
registration_date = models.DateField(
auto_now_add=True, verbose_name=_("date d'inscription")
)
class Meta:
verbose_name = _("Profil BDS")
verbose_name_plural = _("Profils BDS")
def __str__(self):
return self.user.username

View file

@ -1 +0,0 @@
# Create your views here.

View file

View file

@ -1,5 +0,0 @@
from django.contrib import admin
from clubs.models import Club
admin.site.register(Club)

View file

@ -1,5 +0,0 @@
from django.apps import AppConfig
class ClubsConfig(AppConfig):
name = "clubs"

View file

@ -1,46 +0,0 @@
# Generated by Django 2.2.6 on 2019-10-06 17:57
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
operations = [
migrations.CreateModel(
name="Club",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
max_length=1000, unique=True, verbose_name="nom du club"
),
),
(
"description",
models.TextField(blank=True, verbose_name="description"),
),
(
"respos",
models.ManyToManyField(
blank=True,
to=settings.AUTH_USER_MODEL,
verbose_name="responsables du club",
),
),
],
)
]

View file

@ -1,16 +0,0 @@
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
User = get_user_model()
class Club(models.Model):
name = models.CharField(_("nom du club"), max_length=1000, unique=True)
description = models.TextField(_("description"), blank=True)
respos = models.ManyToManyField(
User, verbose_name=_("responsables du club"), blank=True
)
def __str__(self):
return self.name

View file

View file

View file

@ -1,5 +0,0 @@
from django.contrib.staticfiles.apps import StaticFilesConfig
class IgnoreSrcStaticFilesConfig(StaticFilesConfig):
ignore_patterns = StaticFilesConfig.ignore_patterns + ["src/**"]

View file

@ -1,8 +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,11 +0,0 @@
# -*- encoding: utf-8 -*-
"""
English formatting.
"""
from __future__ import unicode_literals
DATETIME_FORMAT = r"l N j, Y \a\t P"
DATE_FORMAT = r"l N j, Y"
TIME_FORMAT = r"P"

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