1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 09:58:23 +02:00

Compare commits

..

147 Commits

Author SHA1 Message Date
bb9f58e497 Migrate to Django 5.0, change requirements and tests, remove ifnotequal, remove htcpcp and CAS Server, adapt Picker, fix alias (lookup) and logout, change URL to re_path 2024-07-11 13:05:43 +02:00
201d6b114a Merge branch 'new_logo' into 'main'
New logo

See merge request bde/nk20!247
2024-06-03 22:00:03 +02:00
19e77df299 Merge branch 'main' into 'new_logo'
# Conflicts:
#   .gitlab-ci.yml
2024-06-03 21:59:44 +02:00
5fd6ec5668 Merge branch 'charte_info' into 'main'
Charte info

See merge request bde/nk20!248
2024-06-03 21:53:01 +02:00
10a01c5bc2 linters 2024-05-30 20:21:56 +02:00
989905ea64 Update .gitlab-ci.yml 2024-05-26 18:41:49 +02:00
0218d43a17 Update .gitlab-ci.yml 2024-05-26 16:00:26 +02:00
5d30b0e819 charte info 2024-05-26 15:46:50 +02:00
ec759dd3c0 error py37-django22 2024-05-23 22:38:09 +02:00
2eb965291d new_logo 2024-05-23 21:46:01 +02:00
7f182ee2ee Merge branch 'traduction_inclusive_fr' into 'main'
Réécriture en inclusif de l'ensemble des textes français de la note

See merge request bde/nk20!246
2024-03-30 13:24:06 +01:00
3132aa4c38 Prise en compte des commentaires de Korenstin 2024-03-30 12:44:51 +01:00
c7eb774859 Prise en compte des commentaires 2024-03-30 11:20:23 +01:00
32f8d285b3 Prise en compte des commentaires 2024-03-30 11:12:33 +01:00
050256ea13 Réécriture en inclusif de l'ensemble des textes français de la note 2024-03-29 17:59:43 +01:00
7afd15b1cc Merge branch 'invoice_modification' into 'main'
changement template facture

Closes #128

See merge request bde/nk20!243
2024-03-27 19:10:40 +01:00
258361f116 Update forms.py 2024-03-27 10:25:38 +01:00
a307530579 Merge branch 'change_date' into 'main'
change date

See merge request bde/nk20!245
2024-03-27 10:19:37 +01:00
5de930bf40 Update forms.py 2024-03-27 10:04:14 +01:00
f7ebe0e99b Update forms.py 2024-03-27 09:43:49 +01:00
73de6e2176 Update forms.py 2024-03-27 09:20:32 +01:00
201611b105 change date 2024-03-26 08:33:34 +01:00
40c239e9da Update models.py 2024-03-24 16:41:18 +01:00
2aaab2b454 Update test_treasury.py 2024-03-24 15:55:46 +01:00
fc088dec86 Update test_treasury.py 2024-03-24 15:20:46 +01:00
2d60f1fd7b Merge branch 'patch_sort' into 'main'
patch sort and optional description

See merge request bde/nk20!244
2024-03-23 21:07:03 +01:00
7b48b09329 patch sort and optional description 2024-03-23 14:32:31 +01:00
ffac940511 changement template facture 2024-03-22 18:22:08 +01:00
50f98fd5ad Merge branch 'prez-perm' into 'main'
changed permission for club president

See merge request bde/nk20!242
2024-03-22 12:56:09 +01:00
402e19d1ce changed permission for club president 2024-03-22 12:27:08 +01:00
0b0394b61f Merge branch 'image_fix' into 'main'
réparation photo de profil

See merge request bde/nk20!241
2024-03-21 20:57:56 +01:00
98422d8259 réparation photo de profil 2024-03-21 18:37:47 +01:00
29509b5b26 Merge branch 'quark-main-patch-96792' into 'main'
Changement couleur de la note

See merge request bde/nk20!240
2024-03-14 17:38:29 +01:00
0d64ad31e0 Update custom.css 2024-03-14 17:22:41 +01:00
5781cbd6a5 Merge branch 'quark-main-patch-83351' into 'main'
Changement couleur de la note

See merge request bde/nk20!239
2024-03-14 16:16:37 +01:00
5295e61a00 Changement couleur de la note 2024-03-14 15:59:53 +01:00
e79ed6226a Merge branch 'quark-main-patch-51348' into 'main'
Upload New Migration (change bde)

See merge request bde/nk20!238
2024-03-11 16:28:41 +01:00
68152e6354 Upload New Migration (change bde) 2024-03-11 16:11:54 +01:00
b8cc297baf Merge branch 'quark-main-patch-c661' into 'main'
Update facture template

See merge request bde/nk20!236
2024-03-09 16:25:45 +01:00
cd8224f2e0 Upload New File 2024-03-09 16:06:39 +01:00
3c882a7854 Delete RavePartlist_bg.png 2024-03-09 16:06:01 +01:00
357e1bbaa2 Replace RavePartlist_bg.png 2024-03-09 16:05:29 +01:00
f5c4c58525 Replace RavePartlist_bg.png 2024-03-09 14:03:17 +01:00
dafb602b08 Update models.py 2024-03-09 13:40:45 +01:00
5b377e6a75 Update facture template 2024-03-09 13:04:33 +01:00
28bd62531e Merge branch 'docs-append' into 'main'
Add : Documentation years flag for Extract ML Registrations

See merge request bde/nk20!235
2024-03-08 19:51:12 +01:00
b3a31c27a5 Add : Documentation years flag for Extract ML Registrations 2024-03-08 19:34:48 +01:00
c7a8e6a1a5 Merge branch 'fin_de_campagne' into 'main'
Remove BDE compaign banner

See merge request bde/nk20!234
2024-02-16 16:58:42 +01:00
546a3a72b1 Remove BDE compaign banner 2024-02-15 10:32:39 +01:00
2e5664f79d Merge branch 'Compromis' into 'main'
Update base.html compromis

See merge request bde/nk20!233
2024-02-13 23:27:32 +01:00
e367666fe9 Update base.html compromis 2024-02-13 23:27:11 +01:00
04a9b3daf0 Merge branch 'Revanche' into 'main'
Update base.html

See merge request bde/nk20!232
2024-02-13 21:25:16 +01:00
d1df8f3eac Update base.html
📢Pour la meilleure liste BDE
2024-02-13 21:24:23 +01:00
a5221f66ef Merge branch 'main' into 'main'
Compaign banner

See merge request bde/nk20!231
2024-02-13 14:58:31 +01:00
7d59cd6cd2 Compaign banner 2024-02-13 14:26:28 +01:00
0db0474217 Merge branch 'Update_2024_Copyright' into 'main'
Update 131 files

See merge request bde/nk20!229
2024-02-11 17:29:46 +01:00
2b3eb15f59 fix one copyright and a string before merge 2024-02-11 16:58:53 +01:00
a6b479db19 Update 131 files
- /apps/activity/api/serializers.py
- /apps/activity/api/urls.py
- /apps/activity/api/views.py
- /apps/activity/tests/test_activities.py
- /apps/activity/__init__.py
- /apps/activity/admin.py
- /apps/activity/apps.py
- /apps/activity/forms.py
- /apps/activity/tables.py
- /apps/activity/urls.py
- /apps/activity/views.py
- /apps/api/__init__.py
- /apps/api/apps.py
- /apps/api/serializers.py
- /apps/api/tests.py
- /apps/api/urls.py
- /apps/api/views.py
- /apps/api/viewsets.py
- /apps/logs/signals.py
- /apps/logs/apps.py
- /apps/logs/__init__.py
- /apps/logs/api/serializers.py
- /apps/logs/api/urls.py
- /apps/logs/api/views.py
- /apps/member/api/serializers.py
- /apps/member/api/urls.py
- /apps/member/api/views.py
- /apps/member/templatetags/memberinfo.py
- /apps/member/__init__.py
- /apps/member/admin.py
- /apps/member/apps.py
- /apps/member/auth.py
- /apps/member/forms.py
- /apps/member/hashers.py
- /apps/member/signals.py
- /apps/member/tables.py
- /apps/member/urls.py
- /apps/member/views.py
- /apps/note/api/serializers.py
- /apps/note/api/urls.py
- /apps/note/api/views.py
- /apps/note/models/__init__.py
- /apps/note/static/note/js/consos.js
- /apps/note/templates/note/mails/negative_balance.txt
- /apps/note/templatetags/getenv.py
- /apps/note/templatetags/pretty_money.py
- /apps/note/tests/test_transactions.py
- /apps/note/__init__.py
- /apps/note/admin.py
- /apps/note/apps.py
- /apps/note/forms.py
- /apps/note/signals.py
- /apps/note/tables.py
- /apps/note/urls.py
- /apps/note/views.py
- /apps/permission/api/serializers.py
- /apps/permission/api/urls.py
- /apps/permission/api/views.py
- /apps/permission/templatetags/perms.py
- /apps/permission/tests/test_oauth2.py
- /apps/permission/tests/test_permission_denied.py
- /apps/permission/tests/test_permission_queries.py
- /apps/permission/tests/test_rights_page.py
- /apps/permission/__init__.py
- /apps/permission/admin.py
- /apps/permission/backends.py
- /apps/permission/apps.py
- /apps/permission/decorators.py
- /apps/permission/permissions.py
- /apps/permission/scopes.py
- /apps/permission/signals.py
- /apps/permission/tables.py
- /apps/permission/urls.py
- /apps/permission/views.py
- /apps/registration/tests/test_registration.py
- /apps/registration/__init__.py
- /apps/registration/apps.py
- /apps/registration/forms.py
- /apps/registration/tables.py
- /apps/registration/tokens.py
- /apps/registration/urls.py
- /apps/registration/views.py
- /apps/treasury/api/serializers.py
- /apps/treasury/api/urls.py
- /apps/treasury/api/views.py
- /apps/treasury/templatetags/escape_tex.py
- /apps/treasury/tests/test_treasury.py
- /apps/treasury/__init__.py
- /apps/treasury/admin.py
- /apps/treasury/apps.py
- /apps/treasury/forms.py
- /apps/treasury/signals.py
- /apps/treasury/tables.py
- /apps/treasury/urls.py
- /apps/treasury/views.py
- /apps/wei/api/serializers.py
- /apps/wei/api/urls.py
- /apps/wei/api/views.py
- /apps/wei/forms/surveys/__init__.py
- /apps/wei/forms/surveys/base.py
- /apps/wei/forms/surveys/wei2021.py
- /apps/wei/forms/surveys/wei2022.py
- /apps/wei/forms/surveys/wei2023.py
- /apps/wei/forms/__init__.py
- /apps/wei/forms/registration.py
- /apps/wei/management/commands/export_wei_registrations.py
- /apps/wei/management/commands/import_scores.py
- /apps/wei/management/commands/wei_algorithm.py
- /apps/wei/templates/wei/weilist_sample.tex
- /apps/wei/tests/test_wei_algorithm_2021.py
- /apps/wei/tests/test_wei_algorithm_2022.py
- /apps/wei/tests/test_wei_algorithm_2023.py
- /apps/wei/tests/test_wei_registration.py
- /apps/wei/__init__.py
- /apps/wei/admin.py
- /apps/wei/apps.py
- /apps/wei/tables.py
- /apps/wei/urls.py
- /apps/wei/views.py
- /note_kfet/settings/__init__.py
- /note_kfet/settings/base.py
- /note_kfet/settings/development.py
- /note_kfet/settings/secrets_example.py
- /note_kfet/static/js/base.js
- /note_kfet/admin.py
- /note_kfet/inputs.py
- /note_kfet/middlewares.py
- /note_kfet/urls.py
- /note_kfet/views.py
- /note_kfet/wsgi.py
- /entrypoint.sh
2024-02-07 02:26:49 +01:00
048d251f75 Merge branch 'charliep-main-patch-40779' into 'main'
update Copyright 2024

See merge request bde/nk20!228
2024-02-07 02:05:59 +01:00
7b11cb0797 update Copyright 2024 2024-02-07 01:37:43 +01:00
ff3c30517e Merge branch 'happy-new-year' into 'main'
happy new year

See merge request bde/nk20!226
2024-01-11 16:48:06 +01:00
f481ea6acb happy new year (contain annually WEI change and update to follow Django Style Guide) 2024-01-11 16:32:37 +01:00
802fd8c2d7 Merge branch 'search_conso_bugfix' into 'main'
Bugfix

See merge request bde/nk20!225
2023-11-13 14:29:29 +01:00
5209a586a9 Fixed const being redeclared when script is reevaluated 2023-11-08 17:10:05 +01:00
24f54ac876 Merge branch 'search-conso' into 'main'
Added a search tab for the conso page, fixes #58

Closes #58

See merge request bde/nk20!224
2023-10-27 16:45:41 +02:00
988b4c9e88 Linting 2023-10-26 21:03:48 +02:00
e32c267995 Moved js code to the external conso file 2023-10-26 19:10:43 +02:00
5e39209ab1 Made searchbar completely client-based 2023-10-26 19:01:09 +02:00
08b2fabe07 Removing jquery means changing the event API... 2023-10-26 00:22:51 +02:00
405479e5ad Execute script to add behavior to searched buttons 2023-10-26 00:10:56 +02:00
0cc130092f Added a search tab for the conso page 2023-10-25 20:01:48 +02:00
ff6e207512 Merge branch 'beta' into 'main'
check for a model in permission and use that in treasury

See merge request bde/nk20!222
2023-09-29 12:08:00 +02:00
0f1e4d2e60 check for a model in permission and use that in treasury 2023-09-28 18:48:57 +02:00
6255bcbbb1 Merge branch 'beta' into 'main'
Merge beta

See merge request bde/nk20!221
2023-09-27 17:14:49 +02:00
d82a1001c4 Moved transaction through frienships right to basic rights 2023-09-27 16:55:00 +02:00
31a54482f0 Updated doc to tell maintainers to create psql superusers 2023-09-27 16:53:30 +02:00
4ee02345d4 Merge branch 'better-friendship-view' into 'main'
Rework of the friendships page

See merge request bde/nk20!220
2023-09-21 15:48:00 +02:00
422c087d17 fix wei test 2023-09-20 07:04:13 +02:00
30d6e2c95e Added trusts to note admin site 2023-09-19 15:07:30 +02:00
f3a3f07e38 Tweaked message and did missing french translations 2023-09-18 17:29:52 +02:00
a5e802f370 Improved the error message when trying to duplicate a Trust 2023-09-18 17:12:31 +02:00
540f3bc354 regenerated messages so locations are consistent with codebase 2023-09-02 00:04:54 +02:00
2d19457506 Add spanish translation for friendship 2023-09-01 17:35:52 +02:00
72786d0d2b Translated js strings, unified some case 2023-09-01 17:34:52 +02:00
f099cbc879 Linting 2023-09-01 17:32:29 +02:00
977eb7c0d4 Generated translation files, did french 2023-09-01 17:30:38 +02:00
d81b1f2710 Tweaked trust back display 2023-09-01 17:15:24 +02:00
6a69590a82 Added a 'trust back' button, front can be improved 2023-09-01 17:15:24 +02:00
7afc583282 Made trust adding widget resetable, corrected the unexpected empty field behavior and improved autocomplete's responsiveness 2023-09-01 17:15:24 +02:00
4fb0b7d736 First pass on a display of users trusting you, added a corresponding right 2023-09-01 17:15:13 +02:00
18a5b65a1c Merge branch 'VSS' into 'main'
anti VSS

See merge request bde/nk20!219
2023-08-31 15:58:52 +02:00
f545af4977 typo 2023-08-31 15:40:49 +02:00
103e2d0635 add GC anti-VSS 2023-08-31 15:25:44 +02:00
aedf0e87ba prez BDE can block note 2023-08-31 13:46:27 +02:00
dab45b5fd4 translation 2023-08-31 13:40:53 +02:00
b3353b563c add VSS checkbox on registration 2023-08-31 12:21:38 +02:00
6bc52be707 Merge branch 'WEI_with_questions' into 'main'
Wei with questions

See merge request bde/nk20!218
2023-08-31 12:01:39 +02:00
834d68fe35 typo 2023-08-31 11:45:17 +02:00
c6a2849d35 test 2023-08-30 16:16:29 +02:00
4ab22c92b3 After WEI registration validation, come back to unvalidate registration page 2023-08-30 09:52:17 +02:00
c328c1457c add register button at the end of WEI registration 2023-08-28 22:27:45 +02:00
96da7d01ae change on a field that everyone have (1A don't have bus) 2023-08-28 19:26:51 +02:00
d27f942339 typo 2023-08-28 10:13:28 +02:00
738d6c932d questions ! 2023-08-28 00:42:33 +02:00
1760196578 more tests 2023-08-27 23:11:40 +02:00
13b9b6edea tests 2023-08-27 18:09:46 +02:00
e06e3b2972 one question by page 2023-08-26 23:47:10 +02:00
9596aa7b8c base for questions instead of words 2023-08-26 17:52:48 +02:00
ba0d64f0d4 Merge branch 'new_default_year' into 'main'
new default year

See merge request bde/nk20!217
2023-08-23 23:53:45 +02:00
8d17801e28 new default year 2023-08-23 23:32:01 +02:00
609362c4f8 Merge branch 'update_permission' into 'main'
Update permission

See merge request bde/nk20!216
2023-08-23 22:50:24 +02:00
b6e68eeebe Merge branch 'charliep-main-patch-47507' into 'main'
Update forms.py - Homogénéisation des cases

See merge request bde/nk20!215
2023-08-08 15:39:44 +02:00
6410542027 Update forms.py - Homogénéisation des cases 2023-08-08 15:38:29 +02:00
f1cc82fab3 Merge branch 'linters' into 'main'
Linters

See merge request bde/nk20!214
2023-07-17 09:27:22 +02:00
644cf14c4b missing brackets 2023-07-17 09:11:25 +02:00
f19a489313 linters (removing B019) 2023-07-17 08:50:10 +02:00
dedd6c69cc new commits in nk20-scripts 2023-07-17 06:58:01 +02:00
b42f5afeab Merge branch 'registration2023' into 'main'
Registration2023

See merge request bde/nk20!213
2023-07-16 17:12:33 +02:00
b08da7a727 help text on WEI emergency contact 2023-07-09 14:57:48 +02:00
451aa64f33 Unisexe clothing cut 2023-07-09 12:30:23 +02:00
3c99b0f3e9 do not change transactions date when validating/deleting credit-soge (and typo) 2023-07-09 11:23:33 +02:00
201a179947 linters 2023-07-09 10:36:36 +02:00
96784aee3b remove (comment) soge from registration 2023-07-07 21:44:18 +02:00
981c4d0300 fix update of club membership start/end date 2023-07-07 20:39:19 +02:00
11223430fd Merge branch 'WEI2023' into 'main'
Préparation WEI 2023

See merge request bde/nk20!212
2023-07-04 19:17:17 +02:00
7aeb977e72 Oubli dans le fichier test_wei_registration_.py d'un 2022 en 2023 2023-07-04 18:33:54 +02:00
52fef1df42 Préparation WEI 2023 2023-07-04 18:23:43 +02:00
16f8a60a3f possibilité de l'adhésion au BDA lors de l'inscription 2023-07-04 17:32:48 +02:00
d5819ac562 Merge branch 'FAQ' into 'main'
Ajout d'un lien vers la FAQ de la note.

See merge request bde/nk20!209
2023-04-18 15:51:38 +02:00
a79df8f1f6 Merge branch 'invoice_bg_storlist' into 'main'
changement du fond des factures

See merge request bde/nk20!211
2023-04-14 19:29:26 +02:00
364b18e188 migrations 2023-04-14 16:52:46 +02:00
10a883b2e5 new treasury phone number 2023-04-14 16:00:48 +02:00
1410ab6c4f Almost on time, the SIRET number is now changed 2023-04-14 15:35:18 +02:00
623dd61be6 Remove phone number 2023-04-14 14:56:34 +02:00
48a0a87e7c changement du fond des factures 2023-04-14 00:25:26 +02:00
563f525b11 Merge branch 'cron' into 'main'
fréquence des mails de négatif aux trez : 1 mois -> 1 semaine, et les notes liées au BDE n'apparaissent plus

See merge request bde/nk20!210
2023-04-08 13:04:59 +02:00
63c1d74f1a Ignore notes containing '- BDE-' in the list of negative balances 2023-04-07 15:47:06 +02:00
c42fb380a6 frequence des mails de négatif aux trez : 1 mois -> 1 semiane 2023-04-06 09:04:27 +02:00
c636d52a73 traduction (allemand et espagnol probablement pas optimal) 2023-03-31 17:21:58 +02:00
6a9021ec14 Merge branch 'couleur_totalist_spies' into 'main'
Couleur totalist spies

See merge request bde/nk20!208
2023-03-31 12:37:24 +02:00
9c9149b53a Ajout d'un lien vers la FAQ de la note. 2023-03-31 12:34:14 +02:00
cb74311e7b Commit migration, j'étais triggered 2023-03-30 19:14:52 +02:00
9d7dd566c9 Ignore /tmp/ 2023-03-30 17:26:06 +02:00
9944ebcaad changement des couleurs de la note vers les couleurs totalist spies 2023-03-25 02:13:16 +01:00
8537f043f7 changement des couleurs de la note vers les couleurs totalist spies 2023-03-25 00:57:19 +01:00
c89a95f8d2 Merge branch 'invoice-logo-totalist' into 'main'
changement du fond des factures

See merge request bde/nk20!207
2023-01-30 13:06:39 +01:00
73640b1dfa changement du fond des factures 2023-01-30 00:06:45 +01:00
194 changed files with 3424 additions and 1936 deletions

1
.gitignore vendored
View File

@ -42,6 +42,7 @@ map.json
backups/ backups/
/static/ /static/
/media/ /media/
/tmp/
# Virtualenv # Virtualenv
env/ env/

View File

@ -7,25 +7,10 @@ stages:
variables: variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Debian Buster # Ubuntu 22.04
py37-django22: py310-django50:
stage: test stage: test
image: debian:buster-backports image: ubuntu:22.04
before_script:
- >
apt-get update &&
apt-get install --no-install-recommends -t buster-backports -y
python3-django python3-django-crispy-forms
python3-django-extensions python3-django-filters python3-django-polymorphic
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py37-django22
# Ubuntu 20.04
py38-django22:
stage: test
image: ubuntu:20.04
before_script: before_script:
# Fix tzdata prompt # Fix tzdata prompt
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
@ -37,12 +22,12 @@ py38-django22:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py38-django22 script: tox -e py310-django50
# Debian Bullseye # Debian Bookworm
py39-django22: py311-django50:
stage: test stage: test
image: debian:bullseye image: debian:bookworm
before_script: before_script:
- > - >
apt-get update && apt-get update &&
@ -52,11 +37,11 @@ py39-django22:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py39-django22 script: tox -e py311-django50
linters: linters:
stage: quality-assurance stage: quality-assurance
image: debian:buster-backports image: debian:bullseye
before_script: before_script:
- apt-get update && apt-get install -y tox - apt-get update && apt-get install -y tox
script: tox -e linters script: tox -e linters

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers from rest_framework import serializers

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,6 +1,8 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from datetime import timedelta from datetime import timedelta
from random import shuffle from random import shuffle
@ -10,7 +12,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
from note.models import Note, NoteUser from note.models import Note, NoteUser
from note_kfet.inputs import Autocomplete, DateTimePickerInput from note_kfet.inputs import Autocomplete
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2024-03-23 13:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('activity', '0002_auto_20200904_2341'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='description',
field=models.TextField(blank=True, default='', verbose_name='description'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
@ -66,6 +66,8 @@ class Activity(models.Model):
description = models.TextField( description = models.TextField(
verbose_name=_('description'), verbose_name=_('description'),
blank=True,
default="",
) )
location = models.CharField( location = models.CharField(
@ -123,6 +125,14 @@ class Activity(models.Model):
verbose_name=_('open'), verbose_name=_('open'),
) )
class Meta:
verbose_name = _("activity")
verbose_name_plural = _("activities")
unique_together = ("name", "date_start", "date_end",)
def __str__(self):
return self.name
@transaction.atomic @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
@ -144,14 +154,6 @@ class Activity(models.Model):
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities() if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
return ret return ret
def __str__(self):
return self.name
class Meta:
verbose_name = _("activity")
verbose_name_plural = _("activities")
unique_together = ("name", "date_start", "date_end",)
class Entry(models.Model): class Entry(models.Model):
""" """
@ -252,14 +254,13 @@ class Guest(models.Model):
verbose_name=_("inviter"), verbose_name=_("inviter"),
) )
@property class Meta:
def has_entry(self): verbose_name = _("guest")
try: verbose_name_plural = _("guests")
if self.entry: unique_together = ("activity", "last_name", "first_name", )
return True
return False def __str__(self):
except AttributeError: return self.first_name + " " + self.last_name
return False
@transaction.atomic @transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
@ -290,13 +291,14 @@ class Guest(models.Model):
return super().save(force_insert, force_update, using, update_fields) return super().save(force_insert, force_update, using, update_fields)
def __str__(self): @property
return self.first_name + " " + self.last_name def has_entry(self):
try:
class Meta: if self.entry:
verbose_name = _("guest") return True
verbose_name_plural = _("guests") return False
unique_together = ("activity", "last_name", "first_name", ) except AttributeError:
return False
class GuestTransaction(Transaction): class GuestTransaction(Transaction):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone from django.utils import timezone

View File

@ -17,4 +17,27 @@ SPDX-License-Identifier: GPL-3.0-or-later
</form> </form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extrajavascript %}
<script>
var date_end = document.getElementById("id_date_end");
var date_start = document.getElementById("id_date_start");
function update_date_end (){
if(date_end.value=="" || date_end.value<date_start.value){
date_end.value = date_start.value;
};
};
function update_date_start (){
if(date_start.value=="" || date_end.value<date_start.value){
date_start.value = date_end.value;
};
};
date_start.addEventListener('focusout', update_date_end);
date_end.addEventListener('focusout', update_date_start);
</script>
{% endblock %}

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta from datetime import timedelta

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from hashlib import md5 from hashlib import md5
@ -76,6 +76,7 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
context['upcoming'] = ActivityTable( context['upcoming'] = ActivityTable(
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
prefix='upcoming-', prefix='upcoming-',
order_by='date_start',
) )
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,5 +1,5 @@
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination): class CustomPagination(PageNumberPagination):
page_size_query_param = 'page_size' page_size_query_param = 'page_size'

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json import json

View File

@ -1,8 +1,8 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.conf.urls import url, include from django.urls import include, re_path
from rest_framework import routers from rest_framework import routers
from .views import UserInformationView from .views import UserInformationView
@ -47,7 +47,7 @@ app_name = 'api'
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
urlpatterns = [ urlpatterns = [
url('^', include(router.urls)), re_path('^', include(router.urls)),
url('^me/', UserInformationView.as_view()), re_path('^me/', UserInformationView.as_view()),
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), re_path('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.models import User from django.contrib.auth.models import User

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers from rest_framework import serializers

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import ChangelogViewSet from .views import ChangelogViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
@ -76,9 +76,6 @@ class Changelog(models.Model):
verbose_name=_('timestamp'), verbose_name=_('timestamp'),
) )
def delete(self, using=None, keep_parents=False):
raise ValidationError(_("Logs cannot be destroyed."))
class Meta: class Meta:
verbose_name = _("changelog") verbose_name = _("changelog")
verbose_name_plural = _("changelogs") verbose_name_plural = _("changelogs")
@ -86,3 +83,6 @@ class Changelog(models.Model):
def __str__(self): def __str__(self):
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format( return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp)) action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
def delete(self, using=None, keep_parents=False):
raise ValidationError(_("Logs cannot be destroyed."))

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers from rest_framework import serializers

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet from .views import ProfileViewSet, ClubViewSet, MembershipViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from cas_server.auth import DjangoAuthUser # pragma: no cover from cas_server.auth import DjangoAuthUser # pragma: no cover

View File

@ -1,8 +1,9 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import io import io
from bootstrap_datepicker_plus.widgets import DatePickerInput
from PIL import Image, ImageSequence from PIL import Image, ImageSequence
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -13,7 +14,7 @@ from django.forms import CheckboxSelectMultiple
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput from note_kfet.inputs import Autocomplete, AmountInput
from permission.models import PermissionMask, Role from permission.models import PermissionMask, Role
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
@ -32,7 +33,7 @@ class UserForm(forms.ModelForm):
# Django usernames can only contain letters, numbers, @, ., +, - and _. # Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames: # We want to allow users to have uncommon and unpractical usernames:
# That is their problem, and we have normalized aliases for us. # That is their problem, and we have normalized aliases for us.
return super()._get_validation_exclusions() + ["username"] return super()._get_validation_exclusions() | {"username"}
class Meta: class Meta:
model = User model = User
@ -47,6 +48,13 @@ class ProfileForm(forms.ModelForm):
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
VSS_charter_read = forms.BooleanField(
required=True,
label=_("Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"),
help_text=_("Tick after having read and accepted the anti-VSS charter \
<a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> available here in pdf</a>")
)
def clean_promotion(self): def clean_promotion(self):
promotion = self.cleaned_data["promotion"] promotion = self.cleaned_data["promotion"]
if promotion > timezone.now().year: if promotion > timezone.now().year:
@ -114,7 +122,7 @@ class ImageForm(forms.Form):
frame = frame.crop((x, y, x + w, y + h)) frame = frame.crop((x, y, x + w, y + h))
frame = frame.resize( frame = frame.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH), (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS, Image.LANCZOS,
) )
frames.append(frame) frames.append(frame)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import hashlib import hashlib

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.26 on 2022-09-04 21:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0008_auto_20211005_1544'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2023-08-23 21:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0009_auto_20220904_2325'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2023, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2023-08-31 09:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0010_new_default_year'),
]
operations = [
migrations.AddField(
model_name='profile',
name='VSS_charter_read',
field=models.BooleanField(default=False, verbose_name='VSS charter read'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import datetime import datetime
@ -28,7 +28,6 @@ class Profile(models.Model):
We do not want to patch the Django Contrib :model:`auth.User`model; We do not want to patch the Django Contrib :model:`auth.User`model;
so this model add an user profile with additional information. so this model add an user profile with additional information.
""" """
user = models.OneToOneField( user = models.OneToOneField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -134,6 +133,22 @@ class Profile(models.Model):
default=False, default=False,
) )
VSS_charter_read = models.BooleanField(
verbose_name=_("VSS charter read"),
default=False
)
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return str(self.user)
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
@property @property
def ens_year(self): def ens_year(self):
""" """
@ -158,17 +173,6 @@ class Profile(models.Model):
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
return False return False
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
def __str__(self):
return str(self.user)
def send_email_validation_link(self): def send_email_validation_link(self):
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
token = email_validation_token.make_token(self.user) token = email_validation_token.make_token(self.user)
@ -200,9 +204,11 @@ class Club(models.Model):
max_length=255, max_length=255,
unique=True, unique=True,
) )
email = models.EmailField( email = models.EmailField(
verbose_name=_('email'), verbose_name=_('email'),
) )
parent_club = models.ForeignKey( parent_club = models.ForeignKey(
'self', 'self',
null=True, null=True,
@ -253,25 +259,12 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'), help_text=_('Maximal date of a membership, after which members must renew it.'),
) )
def update_membership_dates(self): class Meta:
""" verbose_name = _("club")
This function is called each time the club detail view is displayed. verbose_name_plural = _("clubs")
Update the year of the membership dates.
"""
if not self.membership_start or not self.membership_end:
return
today = datetime.date.today() def __str__(self):
return self.name
if (today - self.membership_start).days >= 365:
if self.membership_start:
self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day)
if self.membership_end:
self.membership_end = datetime.date(self.membership_end.year + 1,
self.membership_end.month, self.membership_end.day)
self._force_save = True
self.save(force_update=True)
@transaction.atomic @transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, def save(self, force_insert=False, force_update=False, using=None,
@ -284,16 +277,29 @@ class Club(models.Model):
self.membership_end = None self.membership_end = None
super().save(force_insert, force_update, update_fields) super().save(force_insert, force_update, update_fields)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk,)) return reverse_lazy('member:club_detail', args=(self.pk,))
def update_membership_dates(self):
"""
This function is called each time the club detail view is displayed.
Update the year of the membership dates.
"""
if not self.membership_start or not self.membership_end:
return
today = datetime.date.today()
while (today - self.membership_start).days >= 365:
if self.membership_start:
self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day)
if self.membership_end:
self.membership_end = datetime.date(self.membership_end.year + 1,
self.membership_end.month, self.membership_end.day)
self._force_save = True
self.save(force_update=True)
class Membership(models.Model): class Membership(models.Model):
""" """
@ -333,6 +339,66 @@ class Membership(models.Model):
verbose_name=_('fee'), verbose_name=_('fee'),
) )
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
@property @property
def valid(self): def valid(self):
""" """
@ -410,58 +476,6 @@ class Membership(models.Model):
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() parent_membership.save()
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
def make_transaction(self): def make_transaction(self):
""" """
Create Membership transaction associated to this membership. Create Membership transaction associated to this membership.
@ -499,11 +513,3 @@ class Membership(models.Model):
soge_credit.save() soge_credit.save()
else: else:
transaction.save(force_insert=True) transaction.save(force_insert=True)
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,7 +1,7 @@
/** /**
* On form submit, create a new friendship * On form submit, create a new friendship
*/ */
function create_trust (e) { function form_create_trust (e) {
// Do not submit HTML form // Do not submit HTML form
e.preventDefault() e.preventDefault()
@ -14,25 +14,35 @@ function create_trust (e) {
addMsg(gettext("You can't add yourself as a friend"), "danger") addMsg(gettext("You can't add yourself as a friend"), "danger")
return return
} }
$.post('/api/note/trust/', { create_trust(formData.get('trusting'), trusted_alias.note)
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
trusting: formData.get('trusting'),
trusted: trusted_alias.note
}).done(function () {
// Reload table
$('#trust_table').load(location.pathname + ' #trust_table')
addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}).fail(function (xhr, _textStatus, _error) { }).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON) errMsg(xhr.responseJSON)
}) })
} }
/** /**
* On click of "delete", delete the alias * Create a trust between users
* @param button_id:Integer Alias id to remove * @param trusting:Integer trusting note id
* @param trusted:Integer trusted note id
*/
function create_trust(trusting, trusted) {
$.post('/api/note/trust/', {
trusting: trusting,
trusted: trusted,
csrfmiddlewaretoken: CSRF_TOKEN
}).done(function () {
// Reload tables
$('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
/**
* On click of "delete", delete the trust
* @param button_id:Integer Trust id to remove
*/ */
function delete_button (button_id) { function delete_button (button_id) {
$.ajax({ $.ajax({
@ -42,6 +52,7 @@ function delete_button (button_id) {
}).done(function () { }).done(function () {
addMsg(gettext('Friendship successfully deleted'), 'success') addMsg(gettext('Friendship successfully deleted'), 'success')
$('#trust_table').load(location.pathname + ' #trust_table') $('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
}).fail(function (xhr, _textStatus, _error) { }).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON) errMsg(xhr.responseJSON)
}) })
@ -49,5 +60,5 @@ function delete_button (button_id) {
$(document).ready(function () { $(document).ready(function () {
// Attach event // Attach event
document.getElementById('form_trust').addEventListener('submit', create_trust) document.getElementById('form_trust').addEventListener('submit', form_create_trust)
}) })

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block profile_content %} {% block profile_content %}
<div class="card bg-light mb-3"> <div class="card bg-light mb-3">
<h3 class="card-header text-center"> <h3 class="card-header text-center">
{% trans "Note friendships" %} {% trans "Add friends" %}
</h3> </h3>
<div class="card-body"> <div class="card-body">
{% if can_create %} {% if can_create %}
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% render_table trusting %} {% render_table trusting %}
</div> </div>
<div class="alert alert-warning card"> <div class="alert alert-warning card mb-3">
{% blocktrans trimmed %} {% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming Adding someone as a friend enables them to initiate transactions coming
from your account (while keeping your balance positive). This is from your account (while keeping your balance positive). This is
@ -33,6 +33,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
friends without needing additional rights among them. friends without needing additional rights among them.
{% endblocktrans %} {% endblocktrans %}
</div> </div>
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "People having you as a friend" %}
</h3>
{% render_table trusted_by %}
</div>
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self): def test_logout(self):
response = self.client.get(reverse("logout")) response = self.client.post(reverse("logout"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_admin_index(self): def test_admin_index(self):

View File

@ -183,7 +183,7 @@ class TestMemberships(TestCase):
club = Club.objects.get(name="Kfet") club = Club.objects.get(name="Kfet")
else: else:
club = Club.objects.create( club = Club.objects.create(
name="Second club " + ("with BDE" if bde_parent else "without BDE"), name="Second club without BDE",
parent_club=None, parent_club=None,
email="newclub@example.com", email="newclub@example.com",
require_memberships=True, require_memberships=True,
@ -335,6 +335,7 @@ class TestMemberships(TestCase):
ml_sports_registration=True, ml_sports_registration=True,
ml_art_registration=True, ml_art_registration=True,
report_frequency=7, report_frequency=7,
VSS_charter_read=True
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.assertTrue(User.objects.filter(username="toto changed").exists()) self.assertTrue(User.objects.filter(username="toto changed").exists())

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, date from datetime import timedelta, date
@ -8,7 +8,6 @@ from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
from django.db import transaction from django.db import transaction
from django.db.models import Q, F from django.db.models import Q, F
from django.shortcuts import redirect from django.shortcuts import redirect
@ -21,13 +20,13 @@ from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from note.models import Alias, NoteClub, NoteUser, Trust from note.models import Alias, NoteClub, NoteUser, Trust
from note.models.transactions import Transaction, SpecialTransaction from note.models.transactions import Transaction, SpecialTransaction
from note.tables import HistoryTable, AliasTable, TrustTable from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
from note_kfet.middlewares import _set_current_request from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
CustomAuthenticationForm, MembershipRolesForm CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -258,17 +257,18 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
note = context['object'].note note = context['object'].note
context["trusting"] = TrustTable( context["trusting"] = TrustTable(
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["trusted_by"] = TrustedTable(
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust( context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
trusting=context["object"].note, trusting=context["object"].note,
trusted=context["object"].note trusted=context["object"].note
)) ))
context["widget"] = { context["widget"] = {
"name": "trusted", "name": "trusted",
"resetable": True,
"attrs": { "attrs": {
"model_pk": ContentType.objects.get_for_model(Alias).pk,
"class": "autocomplete form-control", "class": "autocomplete form-control",
"id": "trusted", "id": "trusted",
"resetable": True,
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
"name_field": "name", "name_field": "name",
"placeholder": "" "placeholder": ""
@ -753,6 +753,10 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
club = old_membership.club club = old_membership.club
user = old_membership.user user = old_membership.user
# Update club membership date
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
club.update_membership_dates()
form.instance.club = club form.instance.club = club
# Get form data # Get form data

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin
@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
PolymorphicChildModelFilter, PolymorphicParentModelAdmin PolymorphicChildModelFilter, PolymorphicParentModelAdmin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
RecurrentTransaction, MembershipTransaction, SpecialTransaction RecurrentTransaction, MembershipTransaction, SpecialTransaction
from .templatetags.pretty_money import pretty_money from .templatetags.pretty_money import pretty_money
@ -21,6 +21,16 @@ class AliasInlines(admin.TabularInline):
model = Alias model = Alias
class TrustInlines(admin.TabularInline):
"""
Define trusts when editing the trusting note
"""
model = Trust
fk_name = "trusting"
extra = 0
readonly_fields = ("trusted",)
@admin.register(Note, site=admin_site) @admin.register(Note, site=admin_site)
class NoteAdmin(PolymorphicParentModelAdmin): class NoteAdmin(PolymorphicParentModelAdmin):
""" """
@ -92,7 +102,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
""" """
Child for an user note, see NoteAdmin Child for an user note, see NoteAdmin
""" """
inlines = (AliasInlines,) inlines = (AliasInlines, TrustInlines)
# We can't change user after creation or the balance # We can't change user after creation or the balance
readonly_fields = ('user', 'balance') readonly_fields = ('user', 'balance')

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
@ -11,6 +11,7 @@ from member.models import Membership
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from rest_framework.utils import model_meta from rest_framework.utils import model_meta
from rest_framework.validators import UniqueTogetherValidator
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
@ -86,11 +87,9 @@ class TrustSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Trust model = Trust
fields = '__all__' fields = '__all__'
validators = [UniqueTogetherValidator(
def validate(self, attrs): queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
instance = Trust(**attrs) message=_("This friendship already exists"))]
instance.clean()
return attrs
class AliasSerializer(serializers.ModelSerializer): class AliasSerializer(serializers.ModelSerializer):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \ from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet) router.register(path + '/consumer', ConsumerViewSet, basename="consumer")
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import re import re
@ -13,7 +13,7 @@ from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
TrustSerializer TrustSerializer
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
@ -179,19 +179,10 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
# We match first an alias if it is matched without normalization, # We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias. # then if the normalized pattern matches a normalized alias.
queryset = queryset.filter( queryset = queryset.filter(
**{f'name{suffix}': alias_prefix + alias} Q(**{f'name{suffix}': alias_prefix + alias})
).union( | Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
queryset.filter( | Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) )
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True).union(
queryset.filter(
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name") else queryset.order_by("name")

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,13 +1,15 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime from datetime import datetime
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput from note_kfet.inputs import Autocomplete, AmountInput
from .models import TransactionTemplate, NoteClub, Alias from .models import TransactionTemplate, NoteClub, Alias

View File

@ -18,6 +18,7 @@ def create_special_notes(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('note', '0001_initial'), ('note', '0001_initial'),
('logs', '0001_initial'),
] ]
operations = [ operations = [

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0.7 on 2024-07-11 09:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('note', '0006_trust'),
]
operations = [
migrations.AlterField(
model_name='note',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
migrations.AlterField(
model_name='transaction',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import unicodedata import unicodedata
@ -293,6 +293,11 @@ class Alias(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
@staticmethod @staticmethod
def normalize(string): def normalize(string):
""" """
@ -321,11 +326,6 @@ class Alias(models.Model):
pass pass
self.normalized_name = normalized_name self.normalized_name = normalized_name
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
if self.name == str(self.note): if self.name == str(self.note):
raise ValidationError(_("You can't delete your main alias."), raise ValidationError(_("You can't delete your main alias."),

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
amount = models.PositiveIntegerField( amount = models.PositiveIntegerField(
verbose_name=_('amount'), verbose_name=_('amount'),
) )
category = models.ForeignKey( category = models.ForeignKey(
TemplateCategory, TemplateCategory,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
verbose_name = _("transaction template") verbose_name = _("transaction template")
verbose_name_plural = _("transaction templates") verbose_name_plural = _("transaction templates")
def get_absolute_url(self):
return reverse('note:template_update', args=(self.pk,))
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self):
return reverse('note:template_update', args=(self.pk,))
class Transaction(PolymorphicModel): class Transaction(PolymorphicModel):
""" """
@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
amount is store in centimes of currency, making it a positive integer amount is store in centimes of currency, making it a positive integer
value. (from someone to someone else) value. (from someone to someone else)
""" """
source = models.ForeignKey( source = models.ForeignKey(
Note, Note,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
models.Index(fields=['destination']), models.Index(fields=['destination']),
] ]
def __str__(self):
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
@transaction.atomic
def save(self, *args, **kwargs):
"""
When saving, also transfer money between two notes
"""
if self.source.pk == self.destination.pk:
# When source == destination, no money is transferred and no transaction is created
return
self.source = Note.objects.select_for_update().get(pk=self.source_id)
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
# Check that the amounts stay between big integer bounds
diff_source, diff_dest = self.validate()
if not (hasattr(self, '_force_save') and self._force_save) \
and (not self.source.is_active or not self.destination.is_active):
raise ValidationError(_("The transaction can't be saved since the source note "
"or the destination note is not active."))
# If the aliases are not entered, we assume that the used alias is the name of the note
if not self.source_alias:
self.source_alias = str(self.source)
if not self.destination_alias:
self.destination_alias = str(self.destination)
# We save first the transaction, in case of the user has no right to transfer money
super().save(*args, **kwargs)
# Save notes
self.source.refresh_from_db()
self.source.balance += diff_source
self.source._force_save = True
self.source.save()
self.destination.refresh_from_db()
self.destination.balance += diff_dest
self.destination._force_save = True
self.destination.save()
def validate(self): def validate(self):
previous_source_balance = self.source.balance previous_source_balance = self.source.balance
previous_dest_balance = self.destination.balance previous_dest_balance = self.destination.balance
@ -208,46 +252,6 @@ class Transaction(PolymorphicModel):
return source_balance - previous_source_balance, dest_balance - previous_dest_balance return source_balance - previous_source_balance, dest_balance - previous_dest_balance
@transaction.atomic
def save(self, *args, **kwargs):
"""
When saving, also transfer money between two notes
"""
if self.source.pk == self.destination.pk:
# When source == destination, no money is transferred and no transaction is created
return
self.source = Note.objects.select_for_update().get(pk=self.source_id)
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
# Check that the amounts stay between big integer bounds
diff_source, diff_dest = self.validate()
if not (hasattr(self, '_force_save') and self._force_save) \
and (not self.source.is_active or not self.destination.is_active):
raise ValidationError(_("The transaction can't be saved since the source note "
"or the destination note is not active."))
# If the aliases are not entered, we assume that the used alias is the name of the note
if not self.source_alias:
self.source_alias = str(self.source)
if not self.destination_alias:
self.destination_alias = str(self.destination)
# We save first the transaction, in case of the user has no right to transfer money
super().save(*args, **kwargs)
# Save notes
self.source.refresh_from_db()
self.source.balance += diff_source
self.source._force_save = True
self.source.save()
self.destination.refresh_from_db()
self.destination.balance += diff_dest
self.destination._force_save = True
self.destination.save()
@property @property
def total(self): def total(self):
return self.amount * self.quantity return self.amount * self.quantity
@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
def type(self): def type(self):
return _('Transfer') return _('Transfer')
def __str__(self):
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
class RecurrentTransaction(Transaction): class RecurrentTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`. Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
""" """
template = models.ForeignKey( template = models.ForeignKey(
TransactionTemplate, TransactionTemplate,
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
class Meta:
verbose_name = _("recurrent transaction")
verbose_name_plural = _("recurrent transactions")
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
def clean(self): def clean(self):
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save): if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
raise ValidationError( raise ValidationError(
_("The destination of this transaction must equal to the destination of the template.")) _("The destination of this transaction must equal to the destination of the template."))
return super().clean() return super().clean()
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
@property @property
def type(self): def type(self):
return _('Template') return _('Template')
class Meta:
verbose_name = _("recurrent transaction")
verbose_name_plural = _("recurrent transactions")
class SpecialTransaction(Transaction): class SpecialTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to transactions with special notes Special type of :model:`note.Transaction` associated to transactions with special notes
""" """
last_name = models.CharField( last_name = models.CharField(
max_length=255, max_length=255,
verbose_name=_("name"), verbose_name=_("name"),
@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
blank=True, blank=True,
) )
class Meta:
verbose_name = _("Special transaction")
verbose_name_plural = _("Special transactions")
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
@property @property
def type(self): def type(self):
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
@ -325,13 +332,8 @@ class SpecialTransaction(Transaction):
def clean(self): def clean(self):
# SpecialTransaction are only possible with NoteSpecial object # SpecialTransaction are only possible with NoteSpecial object
if self.is_credit() == self.is_debit(): if self.is_credit() == self.is_debit():
raise(ValidationError(_("A special transaction is only possible between a" raise ValidationError(_("A special transaction is only possible between a"
" Note associated to a payment method and a User or a Club"))) " Note associated to a payment method and a User or a Club"))
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
@staticmethod @staticmethod
def validate_payment_form(form): def validate_payment_form(form):
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
return not error return not error
class Meta:
verbose_name = _("Special transaction")
verbose_name_plural = _("Special transactions")
class MembershipTransaction(Transaction): class MembershipTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`. Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
""" """
membership = models.OneToOneField( membership = models.OneToOneField(
'member.Membership', 'member.Membership',
on_delete=models.PROTECT, on_delete=models.PROTECT,

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone from django.utils import timezone

View File

@ -1,4 +1,4 @@
// Copyright (C) 2018-2021 by BDE ENS Paris-Saclay // Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// When a transaction is performed, lock the interface to prevent spam clicks. // When a transaction is performed, lock the interface to prevent spam clicks.
@ -258,3 +258,39 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
}) })
}) })
} }
var searchbar = document.getElementById("search-input")
var search_results = document.getElementById("search-results")
var old_pattern = null;
var firstMatch = null;
/**
* Updates the button search tab
* @param force Forces the update even if the pattern didn't change
*/
function updateSearch(force = false) {
let pattern = searchbar.value
if (pattern === "")
firstMatch = null;
if ((pattern === old_pattern || pattern === "") && !force)
return;
firstMatch = null;
const re = new RegExp(pattern, "i");
Array.from(search_results.children).forEach(function(b) {
if (re.test(b.innerText)) {
b.hidden = false;
if (firstMatch === null) {
firstMatch = b;
}
} else
b.hidden = true;
});
}
searchbar.addEventListener("input", function (e) {
debounce(updateSearch)()
});
searchbar.addEventListener("keyup", function (e) {
if (firstMatch && e.key === "Enter")
firstMatch.click()
});

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import html import html
@ -159,11 +159,11 @@ class TrustTable(tables.Table):
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
show_header = False show_header = False
trusted = tables.Column(attrs={'td': {'class': 'text_center'}}) trusted = tables.Column(attrs={'td': {'class': 'text-center'}})
delete_col = tables.TemplateColumn( delete_col = tables.TemplateColumn(
template_code=DELETE_TEMPLATE, template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('delete')}, extra_context={"delete_trans": _('Delete')},
attrs={ attrs={
'td': { 'td': {
'class': lambda record: 'col-sm-1' 'class': lambda record: 'col-sm-1'
@ -173,6 +173,46 @@ class TrustTable(tables.Table):
verbose_name=_("Delete"),) verbose_name=_("Delete"),)
class TrustedTable(tables.Table):
class Meta:
attrs = {
'class': 'table table condensed table-striped',
'id': 'trusted_table'
}
Model = Trust
fields = ("trusting",)
template_name = "django_tables2/bootstrap4.html"
show_header = False
trusting = tables.Column(attrs={
'td': {'class': 'text-center', 'width': '100%'}})
trust_back = tables.Column(
verbose_name=_("Trust back"),
accessor="pk",
attrs={
'td': {
'class': '',
'id': lambda record: "trust_back_" + str(record.pk),
}
},
)
def render_trust_back(self, record):
user_note = record.trusted
trusting_note = record.trusting
if Trust.objects.filter(trusted=trusting_note, trusting=user_note):
return ""
val = '<button id="'
val += str(record.pk)
val += '" class="btn btn-success btn-sm text-nowrap" \
onclick="create_trust(' + str(record.trusted.pk) + ',' + \
str(record.trusting.pk) + ')">'
val += str(_("Add back"))
val += '</button>'
return mark_safe(val)
class AliasTable(tables.Table): class AliasTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
name="{{ widget.name }}" name="{{ widget.name }}"
{# Other attributes are loaded #} {# Other attributes are loaded #}
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} {% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
{% endfor %}> {% endfor %}>
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text"></span> <span class="input-group-text"></span>

View File

@ -103,6 +103,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
<li class="nav-item">
<a class="nav-link font-weight-bold" data-toggle="tab" href="#search">
{% trans "Search" %}
</a>
</li>
</ul> </ul>
</div> </div>
@ -123,6 +128,20 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="tab-pane" id="search">
<input class="form-control mx-auto d-block mb-3"
placeholder="{% trans "Search button..." %}" type="search" id="search-input"/>
<div class="d-inline-flex flex-wrap justify-content-center" id="search-results">
{% for button in all_buttons %}
{% if button.display %}
<button class="btn btn-outline-dark rounded-0 flex-fill" hidden
id="search_button{{ button.id }}" name="button" value="{{ button.name }}">
{{ button.name }} ({{ button.amount | pretty_money }})
</button>
{% endif %}
{% endfor %}
</div>
</div>
</div> </div>
</div> </div>
@ -163,7 +182,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<script type="text/javascript"> <script type="text/javascript">
{% for button in highlighted %} {% for button in highlighted %}
{% if button.display %} {% if button.display %}
$("#highlighted_button{{ button.id }}").click(function() { document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() {
addConso({{ button.destination_id }}, {{ button.amount }}, addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}"); {{ button.id }}, "{{ button.name|escapejs }}");
@ -174,7 +193,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% for category in categories %} {% for category in categories %}
{% for button in category.templates_filtered %} {% for button in category.templates_filtered %}
{% if button.display %} {% if button.display %}
$("#button{{ button.id }}").click(function() { document.getElementById("button{{ button.id }}").addEventListener("click", function() {
addConso({{ button.destination_id }}, {{ button.amount }}, addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}"); {{ button.id }}, "{{ button.name|escapejs }}");
@ -182,5 +201,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% for button in all_buttons %}
{% if button.display %}
document.getElementById("search_button{{ button.id }}").addEventListener("click", function() {
addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}");
});
{% endif %}
{% endfor %}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@ Ce mail t'a été envoyé parce que le solde de ta Note Kfet
Ton solde actuel est de {{ note.balance|pretty_money }}. Ton solde actuel est de {{ note.balance|pretty_money }}.
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérent·e·s dont le solde
est inférieur à 0 € depuis plus de 24h. est inférieur à 0 € depuis plus de 24h.
Si tu ne comprends pas ton solde, tu peux consulter ton historique Si tu ne comprends pas ton solde, tu peux consulter ton historique

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import template from django import template

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import template from django import template

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from api.tests import TestAPI from api.tests import TestAPI
@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from permission.models import Role from permission.models import Role
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\ from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
TransactionTemplateViewSet, TransactionViewSet TransactionTemplateViewSet, TransactionViewSet
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \ from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json import json
@ -10,12 +10,12 @@ from django.core.exceptions import PermissionDenied
from django.db.models import Q, F from django.db.models import Q, F
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, UpdateView, DetailView from django.views.generic import CreateView, UpdateView, DetailView
from django_tables2 import SingleTableView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django_tables2 import SingleTableView
from activity.models import Entry from activity.models import Entry
from note_kfet.inputs import AmountInput
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin from permission.views import ProtectQuerysetMixin
from note_kfet.inputs import AmountInput
from .forms import TransactionTemplateForm, SearchTransactionForm from .forms import TransactionTemplateForm, SearchTransactionForm
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
@ -190,6 +190,10 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
).order_by('name').all() ).order_by('name').all()
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
context['all_buttons'] = TransactionTemplate.objects.filter(
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
).filter(display=True).order_by('name').all()
return context return context

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-lateré # SPDX-License-Identifier: GPL-3.0-or-lateré
from django.contrib import admin from django.contrib import admin

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers from rest_framework import serializers

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import PermissionViewSet, RoleViewSet from .views import PermissionViewSet, RoleViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from api.viewsets import ReadOnlyProtectedModelViewSet from api.viewsets import ReadOnlyProtectedModelViewSet

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date
@ -198,6 +198,41 @@ class PermissionBackend(ModelBackend):
def has_module_perms(self, user_obj, app_label): def has_module_perms(self, user_obj, app_label):
return False return False
@staticmethod
@memoize
def has_model_perm(request, model, type):
"""
Check is the given user has the permission over a given model for a given action.
The result is then memoized.
:param request: The current request
:param model: The model that the permissions shoud apply
:param type: The type of the permissions: view, change, add or delete
For view action, it is consider possible if user can view or change the model
"""
# Requested by a shell
if request is None:
return False
user_obj = request.user
sess = request.session
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
# OAuth2 Authentication
user_obj = request.auth.user
if user_obj is None or user_obj.is_anonymous:
return False
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
return True
ct = ContentType.objects.get_for_model(model)
if any(PermissionBackend.permissions(request, ct, type)):
return True
if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
return True
return False
def get_all_permissions(self, user_obj, obj=None): def get_all_permissions(self, user_obj, obj=None):
ct = ContentType.objects.get_for_model(obj) ct = ContentType.objects.get_for_model(obj)
return list(self.permissions(get_current_request(), ct, "view")) return list(self.permissions(get_current_request(), ct, "view"))

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import sys import sys
from functools import lru_cache from functools import lru_cache

View File

@ -3073,7 +3073,7 @@
], ],
"query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]", "query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]",
"type": "add", "type": "add",
"mask": 2, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Transférer de l'argent depuis une note amie en restant positif" "description": "Transférer de l'argent depuis une note amie en restant positif"
@ -3093,6 +3093,22 @@
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer un crédit quelconque" "description": "Créer un crédit quelconque"
}
},
{
"model": "permission.permission",
"pk": 198,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusted__noteuser__user\": [\"user\"]}",
"type": "view",
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir ceux nous ayant pour ami, pour toujours"
} }
}, },
{ {
@ -3132,10 +3148,11 @@
187, 187,
188, 188,
189, 189,
190, 190,
191, 191,
195, 195,
196 196,
198
] ]
} }
}, },
@ -3211,7 +3228,8 @@
"name": "Pr\u00e9sident\u00b7e de club", "name": "Pr\u00e9sident\u00b7e de club",
"permissions": [ "permissions": [
62, 62,
142 142,
135
] ]
} }
}, },
@ -3259,6 +3277,8 @@
136, 136,
137, 137,
150, 150,
163,
164,
166, 166,
167, 167,
168, 168,
@ -3578,6 +3598,20 @@
] ]
} }
}, },
{
"model": "permission.role",
"pk": 21,
"fields": {
"for_club": 1,
"name": "GC anti-VSS",
"permissions": [
150,
163,
164,
182
]
}
},
{ {
"model": "wei.weirole", "model": "wei.weirole",
"pk": 12, "pk": 12,

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import functools import functools
@ -26,6 +26,15 @@ class InstancedPermission:
self.mask = mask self.mask = mask
self.kwargs = kwargs self.kwargs = kwargs
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else:
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
def applies(self, obj, permission_type, field_name=None): def applies(self, obj, permission_type, field_name=None):
""" """
Returns True if the permission applies to Returns True if the permission applies to
@ -84,21 +93,11 @@ class InstancedPermission:
# noinspection PyProtectedMember # noinspection PyProtectedMember
self.query = Permission._about(self.raw_query, **self.kwargs) self.query = Permission._about(self.raw_query, **self.kwargs)
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else:
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
class PermissionMask(models.Model): class PermissionMask(models.Model):
""" """
Permissions that are hidden behind a mask Permissions that are hidden behind a mask
""" """
rank = models.PositiveSmallIntegerField( rank = models.PositiveSmallIntegerField(
unique=True, unique=True,
verbose_name=_('rank'), verbose_name=_('rank'),
@ -110,13 +109,13 @@ class PermissionMask(models.Model):
verbose_name=_('description'), verbose_name=_('description'),
) )
def __str__(self):
return self.description
class Meta: class Meta:
verbose_name = _("permission mask") verbose_name = _("permission mask")
verbose_name_plural = _("permission masks") verbose_name_plural = _("permission masks")
def __str__(self):
return self.description
class Permission(models.Model): class Permission(models.Model):
@ -194,16 +193,19 @@ class Permission(models.Model):
verbose_name = _("permission") verbose_name = _("permission")
verbose_name_plural = _("permissions") verbose_name_plural = _("permissions")
def clean(self): def __str__(self):
self.query = json.dumps(json.loads(self.query)) return self.description
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@transaction.atomic @transaction.atomic
def save(self, **kwargs): def save(self, **kwargs):
self.full_clean() self.full_clean()
super().save() super().save()
def clean(self):
self.query = json.dumps(json.loads(self.query))
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@staticmethod @staticmethod
def compute_f(oper, **kwargs): def compute_f(oper, **kwargs):
if isinstance(oper, list): if isinstance(oper, list):
@ -317,9 +319,6 @@ class Permission(models.Model):
# query = self._about(query, **kwargs) # query = self._about(query, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs) return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
def __str__(self):
return self.description
class Role(models.Model): class Role(models.Model):
""" """
@ -344,9 +343,9 @@ class Role(models.Model):
default=None, default=None,
) )
def __str__(self):
return self.name
class Meta: class Meta:
verbose_name = _("role permissions") verbose_name = _("role permissions")
verbose_name_plural = _("role permissions") verbose_name_plural = _("role permissions")
def __str__(self):
return self.name

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework.permissions import DjangoObjectPermissions from rest_framework.permissions import DjangoObjectPermissions

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes from oauth2_provider.scopes import BaseScopes

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import django_tables2 as tables import django_tables2 as tables

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta from datetime import timedelta

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, date from datetime import timedelta, date

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.models import User from django.contrib.auth.models import User

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from collections import OrderedDict from collections import OrderedDict
from datetime import date from datetime import date

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,10 +1,11 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -44,14 +45,14 @@ class SignUpForm(UserCreationForm):
fields = ('first_name', 'last_name', 'username', 'email', ) fields = ('first_name', 'last_name', 'username', 'email', )
class DeclareSogeAccountOpenedForm(forms.Form): # class DeclareSogeAccountOpenedForm(forms.Form):
soge_account = forms.BooleanField( # soge_account = forms.BooleanField(
label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE " # label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE "
"partnership."), # "partnership."),
help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your " # help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
"account, you will have to pay the BDE membership."), # "account, you will have to pay the BDE membership."),
required=False, # required=False,
) # )
class WEISignupForm(forms.Form): class WEISignupForm(forms.Form):
@ -67,11 +68,11 @@ class ValidationForm(forms.Form):
""" """
Validate the inscription of the new users and pay memberships. Validate the inscription of the new users and pay memberships.
""" """
soge = forms.BooleanField( # soge = forms.BooleanField(
label=_("Inscription paid by Société Générale"), # label=_("Inscription paid by Société Générale"),
required=False, # required=False,
help_text=_("Check this case if the Société Générale paid the inscription."), # help_text=_("Check this case if the Société Générale paid the inscription."),
) # )
credit_type = forms.ModelChoiceField( credit_type = forms.ModelChoiceField(
queryset=NoteSpecial.objects, queryset=NoteSpecial.objects,
@ -114,3 +115,12 @@ class ValidationForm(forms.Form):
required=False, required=False,
initial=True, initial=True,
) )
# If the bda exists
# if Club.objects.filter(name__iexact="bda").exists():
# The user can join the bda club at the inscription
# join_bda = forms.BooleanField(
# label=_("Join BDA Club"),
# required=False,
# initial=True,
# )

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