1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-27 11:58:54 +02:00

Compare commits

..

78 Commits

Author SHA1 Message Date
354c79bb82 Inclusif manquant 2024-08-04 13:32:33 +02:00
d595d908c6 Fix tests
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2024-07-30 16:34:20 +02:00
734f5b242d C'est pas moi
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2024-07-30 16:32:19 +02:00
b0c7d43a50 De l'inclusif, partout
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2024-07-30 16:28:47 +02:00
b8f81048a5 Merge branch 'fix_ActivityList' into 'main'
Allow to order the 2 tables and to fix the bug of several activities

See merge request bde/nk20!252
2024-07-18 18:17:06 +02:00
af819f45a1 Merge branch 'remove_picture' into 'main'
Allow you to delete the profile picture

See merge request bde/nk20!250
2024-07-18 18:02:43 +02:00
076d065ffa Merge branch 'main' into 'remove_picture'
# Conflicts:
#   locale/fr/LC_MESSAGES/django.po
2024-07-18 17:52:22 +02:00
2da77d9c17 Merge branch 'fix_join_bda' into 'main'
Fix #126 (join_bda)

Closes #126

See merge request bde/nk20!251
2024-07-18 17:14:23 +02:00
01584d6330 Merge branch 'modif_perm' into 'main'
Modif perm

See merge request bde/nk20!249
2024-07-18 16:54:23 +02:00
4c0a5922c4 Allow to order the 2 tables and to fix the bug of several activities 2024-07-15 22:06:11 +02:00
f90b28fc7c Fix #126 (join_bda) 2024-07-15 14:30:46 +02:00
925e0f26f5 Allow you to delete the profile picture 2024-07-13 17:37:19 +02:00
c912383f86 oups la virgule oublié 2024-06-24 22:36:22 +02:00
32830e43fd Modify permission for negative 2024-06-24 21:21:22 +02:00
11c6a6fa7a modifications permissions consommation pc kfet (Alcool) 2024-06-24 16:57:39 +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
202 changed files with 2156 additions and 2082 deletions

View File

@ -8,19 +8,19 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Debian Buster # Debian Buster
py37-django22: # py37-django22:
stage: test # stage: test
image: debian:buster-backports # image: debian:buster-backports
before_script: # before_script:
- > # - >
apt-get update && # apt-get update &&
apt-get install --no-install-recommends -t buster-backports -y # apt-get install --no-install-recommends -t buster-backports -y
python3-django python3-django-crispy-forms # python3-django python3-django-crispy-forms
python3-django-extensions python3-django-filters python3-django-polymorphic # python3-django-extensions python3-django-filters python3-django-polymorphic
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 py37-django22 # script: tox -e py37-django22
# Ubuntu 20.04 # Ubuntu 20.04
py38-django22: py38-django22:
@ -56,7 +56,7 @@ py39-django22:
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

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "apps/scripts"] [submodule "apps/scripts"]
path = apps/scripts path = apps/scripts
url = https://gitlab.crans.org/bde/nk20-scripts.git url = https://gitlab.crans.org/bde/nk20-scripts

View File

@ -12,7 +12,7 @@ RUN apt-get update && \
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \ python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
python3-bs4 python3-setuptools \ python3-bs4 python3-setuptools \
uwsgi uwsgi-plugin-python3 \ uwsgi uwsgi-plugin-python3 \
texlive-xetex gettext libjs-bootstrap4 && \ texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Instal PyPI requirements # Instal PyPI requirements

View File

@ -23,7 +23,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
$ sudo apt update $ sudo apt update
$ sudo apt install --no-install-recommends -y \ $ sudo apt install --no-install-recommends -y \
ipython3 python3-setuptools python3-venv python3-dev \ ipython3 python3-setuptools python3-venv python3-dev \
texlive-xetex gettext libjs-bootstrap4 git texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
``` ```
2. **Clonage du dépot** là où vous voulez : 2. **Clonage du dépot** là où vous voulez :
@ -55,7 +55,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
(env)$ ./manage.py makemigrations (env)$ ./manage.py makemigrations
(env)$ ./manage.py migrate (env)$ ./manage.py migrate
(env)$ ./manage.py loaddata initial (env)$ ./manage.py loaddata initial
(env)$ ./manage.py createsuperuser # Création d'un utilisateur initial (env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
``` ```
6. Enjoy : 6. Enjoy :
@ -115,7 +115,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \ python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
python3-bs4 python3-setuptools python3-docutils \ python3-bs4 python3-setuptools python3-docutils \
memcached uwsgi uwsgi-plugin-python3 \ memcached uwsgi uwsgi-plugin-python3 \
texlive-xetex gettext libjs-bootstrap4 \ texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
nginx python3-venv git acl nginx python3-venv git acl
``` ```

View File

@ -18,6 +18,7 @@
- ipython3 - ipython3
# Front-end dependencies # Front-end dependencies
- fonts-font-awesome
- libjs-bootstrap4 - libjs-bootstrap4
# Python dependencies # Python dependencies

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,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

@ -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

@ -34,9 +34,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
<div class="card-footer"> <div class="card-footer">
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false"> <a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
<svg class="bi bi-calendar-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-calendar-plus-o" aria-hidden="true"></i>
<path d="M4 .5a.5.5 0 0 0-1 0V1H2a2 2 0 0 0-2 2v1h16V3a2 2 0 0 0-2-2h-1V.5a.5.5 0 0 0-1 0V1H4V.5zM16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2zM8.5 8.5V10H10a.5.5 0 0 1 0 1H8.5v1.5a.5.5 0 0 1-1 0V11H6a.5.5 0 0 1 0-1h1.5V8.5a.5.5 0 0 1 1 0z"/>
</svg>
{% trans 'New activity' %} {% trans 'New activity' %}
</a> </a>
</div> </div>
@ -48,4 +46,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3> </h3>
{% render_table table %} {% render_table table %}
</div> </div>
{% endblock %} {% 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
@ -17,7 +17,8 @@ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView from django.views.generic import DetailView, TemplateView, UpdateView
from django_tables2.views import SingleTableView from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin
from note.models import Alias, NoteSpecial, NoteUser from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
@ -57,26 +58,40 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
""" """
Displays all Activities, and classify if they are on-going or upcoming ones. Displays all Activities, and classify if they are on-going or upcoming ones.
""" """
model = Activity model = Activity
table_class = ActivityTable tables = [ActivityTable, ActivityTable]
ordering = ('-date_start',)
extra_context = {"title": _("Activities")} extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct() return super().get_queryset(**kwargs).distinct()
def get_tables(self):
tables = super().get_tables()
tables[0].prefix = "all-"
tables[1].prefix = "upcoming-"
return tables
def get_tables_data(self):
# first table = all activities, second table = upcoming
return [
self.get_queryset().order_by("-date_start"),
Activity.objects.filter(date_end__gt=timezone.now())
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
.distinct()
.order_by("date_start")
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now()) tables = context["tables"]
context['upcoming'] = ActivityTable( for name, table in zip(["table", "upcoming"], tables):
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), context[name] = table
prefix='upcoming-',
)
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
context["started_activities"] = started_activities context["started_activities"] = started_activities

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,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,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 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
@ -56,13 +56,13 @@ def save_object(sender, instance, **kwargs):
# noinspection PyProtectedMember # noinspection PyProtectedMember
previous = instance._previous previous = instance._previous
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP # Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
request = get_current_request() request = get_current_request()
if request is None: if request is None:
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée # On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info # IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
ip = "127.0.0.1" ip = "127.0.0.1"
username = Alias.normalize(getpass.getuser()) username = Alias.normalize(getpass.getuser())
note = NoteUser.objects.filter(alias__normalized_name=username) note = NoteUser.objects.filter(alias__normalized_name=username)
@ -134,13 +134,13 @@ def delete_object(sender, instance, **kwargs):
if instance._meta.label_lower in EXCLUDED or hasattr(instance, "_no_signal"): if instance._meta.label_lower in EXCLUDED or hasattr(instance, "_no_signal"):
return return
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP # Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
request = get_current_request() request = get_current_request()
if request is None: if request is None:
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée # On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info # IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
ip = "127.0.0.1" ip = "127.0.0.1"
username = Alias.normalize(getpass.getuser()) username = Alias.normalize(getpass.getuser())
note = NoteUser.objects.filter(alias__normalized_name=username) note = NoteUser.objects.filter(alias__normalized_name=username)

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,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 io import io
@ -121,7 +121,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)
@ -138,6 +138,9 @@ class ImageForm(forms.Form):
return cleaned_data return cleaned_data
def is_valid(self):
return super().is_valid() or super().clean().get('image') is None
class ClubForm(forms.ModelForm): class ClubForm(forms.ModelForm):
def clean(self): def clean(self):
@ -151,7 +154,7 @@ class ClubForm(forms.ModelForm):
class Meta: class Meta:
model = Club model = Club
fields = '__all__' exclude = ("add_registration_form",)
widgets = { widgets = {
"membership_fee_paid": AmountInput(), "membership_fee_paid": AmountInput(),
"membership_fee_unpaid": AmountInput(), "membership_fee_unpaid": AmountInput(),
@ -207,9 +210,9 @@ class MembershipForm(forms.ModelForm):
class Meta: class Meta:
model = Membership model = Membership
fields = ('user', 'date_start') fields = ('user', 'date_start')
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion. # Le champ d'utilisateur⋅rice est remplacé par un champ d'auto-complétion.
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
# et récupère les noms d'utilisateur valides # et récupère les noms d'utilisateur⋅rices valides
widgets = { widgets = {
'user': 'user':
Autocomplete( Autocomplete(

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.28 on 2024-07-15 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0011_profile_vss_charter_read'),
]
operations = [
migrations.AddField(
model_name='club',
name='add_registration_form',
field=models.BooleanField(default=False, verbose_name='add to registration form'),
),
]

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,
@ -139,6 +138,17 @@ class Profile(models.Model):
default=False 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):
""" """
@ -163,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)
@ -205,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,
@ -258,6 +259,32 @@ 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.'),
) )
add_registration_form = models.BooleanField(
verbose_name=_("add to registration form"),
default=False,
)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.require_memberships:
self.membership_fee_paid = 0
self.membership_fee_unpaid = 0
self.membership_duration = None
self.membership_start = None
self.membership_end = None
super().save(force_insert, force_update, update_fields)
def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk,))
def update_membership_dates(self): def update_membership_dates(self):
""" """
This function is called each time the club detail view is displayed. This function is called each time the club detail view is displayed.
@ -278,27 +305,6 @@ class Club(models.Model):
self._force_save = True self._force_save = True
self.save(force_update=True) self.save(force_update=True)
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.require_memberships:
self.membership_fee_paid = 0
self.membership_fee_unpaid = 0
self.membership_duration = None
self.membership_start = None
self.membership_end = None
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):
return reverse_lazy('member:club_detail', args=(self.pk,))
class Membership(models.Model): class Membership(models.Model):
""" """
@ -338,6 +344,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):
""" """
@ -407,66 +473,14 @@ class Membership(models.Model):
if self.club.parent_club.name == "BDE": if self.club.parent_club.name == "BDE":
parent_membership.roles.set( parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all()) Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all())
elif self.club.parent_club.name == "Kfet": elif self.club.parent_club.name == "Kfet":
parent_membership.roles.set( parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all()) Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
else: else:
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.
@ -504,11 +518,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,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

@ -45,10 +45,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-footer"> <div class="card-footer">
{% if user_object %} {% if user_object %}
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}"> <a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-edit"></i> {% trans 'Update Profile' %}
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{% trans 'Update Profile' %}
</a> </a>
{% url 'member:user_detail' user_object.pk as user_profile_url %} {% url 'member:user_detail' user_object.pk as user_profile_url %}
{% if request.path_info != user_profile_url %} {% if request.path_info != user_profile_url %}
@ -62,10 +59,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if ".change_"|has_perm:club %} {% if ".change_"|has_perm:club %}
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}" <a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
data-turbolinks="false"> data-turbolinks="false">
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-edit"></i> {% trans 'Update Profile' %}
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{% trans 'Update Profile' %}
</a> </a>
{% endif %} {% endif %}
{% url 'member:club_detail' club.pk as club_detail_url %} {% url 'member:club_detail' club.pk as club_detail_url %}

View File

@ -10,10 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card"> <div class="card">
<div class="card-header position-relative" id="clubListHeading"> <div class="card-header position-relative" id="clubListHeading">
<a class="font-weight-bold"> <a class="font-weight-bold">
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-users"></i> {% trans "Club managers" %}
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
{% trans "Club managers" %}
</a> </a>
</div> </div>
{% render_table managers %} {% render_table managers %}
@ -26,12 +23,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card"> <div class="card">
<div class="card-header position-relative" id="clubListHeading"> <div class="card-header position-relative" id="clubListHeading">
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}"> <a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-users"></i> {% trans "Club members" %}
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
</svg>
{% trans "Club members" %}
</a> </a>
</div> </div>
{% render_table member_list %} {% render_table member_list %}
@ -45,10 +37,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-header position-relative" id="historyListHeading"> <div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %} <a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}> href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-euro"></i> {% trans "Transaction history" %}
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
</svg>
{% trans "Transaction history" %}
</a> </a>
</div> </div>
<div id="history_list"> <div id="history_list">

View File

@ -47,9 +47,7 @@
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
<dd class="col-xl-6"> <dd class="col-xl-6">
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}"> <a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-edit"></i>
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }}) {% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
</a> </a>
</dd> </dd>

View File

@ -11,9 +11,7 @@
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
<dd class="col-xl-6"> <dd class="col-xl-6">
<a class="badge badge-secondary" href="{% url 'password_change' %}"> <a class="badge badge-secondary" href="{% url 'password_change' %}">
<svg class="bi bi-lock" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-lock"></i>
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
</svg>
{% trans 'Change password' %} {% trans 'Change password' %}
</a> </a>
</dd> </dd>
@ -22,9 +20,7 @@
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
<dd class="col-xl-6"> <dd class="col-xl-6">
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}"> <a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-edit"></i>
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }}) {% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
</a> </a>
</dd> </dd>
@ -64,10 +60,7 @@
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<svg class="bi bi-cogs" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-cogs"></i>{% trans 'API token' %}
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
</svg>
{% trans 'API token' %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post" enctype="multipart/form-data" id="formUpload"> <form method="post" enctype="multipart/form-data" id="formUpload">
{% csrf_token %} {% csrf_token %}
{{ form |crispy }} {{ form |crispy }}
{% if user.note.display_image != "pic/default.png" %}
<input type="submit" class="btn btn-primary" value="{% trans "Remove" %}">
{% endif %}
</form> </form>
</div> </div>
<!-- MODAL TO CROP THE IMAGE --> <!-- MODAL TO CROP THE IMAGE -->

View File

@ -18,10 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card bg-light mb-3"> <div class="card bg-light mb-3">
<div class="card-header position-relative" id="clubListHeading"> <div class="card-header position-relative" id="clubListHeading">
<a class="font-weight-bold"> <a class="font-weight-bold">
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-users"></i> {% trans "View my memberships" %}
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
{% trans "View my memberships" %}
</a> </a>
</div> </div>
{% render_table club_list %} {% render_table club_list %}
@ -32,10 +29,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="stretched-link font-weight-bold text-decoration-none" <a class="stretched-link font-weight-bold text-decoration-none"
{% if "note.view_note"|has_perm:user_object.note %} {% if "note.view_note"|has_perm:user_object.note %}
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}> href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-euro"></i> {% trans "Transaction history" %}
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
</svg>
{% trans "Transaction history" %}
</a> </a>
</div> </div>
<div id="history_list"> <div id="history_list">

View File

@ -7,11 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block content %} {% block content %}
{% if can_manage_registrations %} {% if can_manage_registrations %}
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}"> <a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-user-plus"></i> {% trans "Registrations" %}
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
</svg>
{% trans "Registrations" %}
</a> </a>
{% endif %} {% endif %}

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

@ -291,7 +291,7 @@ class TestMemberships(TestCase):
response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict( response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict(
roles=[role.id for role in Role.objects.filter( roles=[role.id for role in Role.objects.filter(
Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).all()], Q(name="Membre de club") | Q(name="Trésorière de club") | Q(name="Bureau de club")).all()],
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.membership.refresh_from_db() self.membership.refresh_from_db()

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
@ -26,7 +26,7 @@ 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
@ -326,12 +326,15 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
"""Save image to note""" """Save image to note"""
image = form.cleaned_data['image'] image = form.cleaned_data['image']
# Rename as a PNG or GIF if image is None:
extension = image.name.split(".")[-1] image = "pic/default.png"
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else: else:
image.name = "{}_pic.png".format(self.object.note.pk) # Rename as a PNG or GIF
extension = image.name.split(".")[-1]
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else:
image.name = "{}_pic.png".format(self.object.note.pk)
# Save # Save
self.object.note.display_image = image self.object.note.display_image = image
@ -824,8 +827,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
ret = super().form_valid(form) ret = super().form_valid(form)
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \ member_role = Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all() \
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \ if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all() \
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all() if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
# Set the same roles as before # Set the same roles as before
if old_membership: if old_membership:
@ -861,7 +864,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
membership.refresh_from_db() membership.refresh_from_db()
if old_membership.exists(): if old_membership.exists():
membership.roles.set(old_membership.get().roles.all()) membership.roles.set(old_membership.get().roles.all())
membership.roles.set(Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all()) membership.roles.set(Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
membership.save() membership.save()
return ret return ret

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

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 .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \ from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \

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

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 datetime from datetime import datetime

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")
@ -328,11 +335,6 @@ class SpecialTransaction(Transaction):
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.
@ -259,8 +259,8 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
}) })
} }
const searchbar = document.getElementById("search-input") var searchbar = document.getElementById("search-input")
const search_results = document.getElementById("search-results") var search_results = document.getElementById("search-results")
var old_pattern = null; var old_pattern = null;
var firstMatch = null; var firstMatch = null;

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

View File

@ -148,10 +148,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{# Mode switch #} {# Mode switch #}
<div class="card-footer border-primary"> <div class="card-footer border-primary">
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}"> <a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <i class="fa fa-edit"></i> {% trans "Edit" %}
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{% trans "Edit" %}
</a> </a>
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons"> <div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
<label for="single_conso" class="btn btn-sm btn-outline-primary active"> <label for="single_conso" class="btn btn-sm btn-outline-primary active">

View File

@ -22,8 +22,8 @@
</p> </p>
<p> <p>
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⋅es dont le solde
est inférieur à 0 € depuis plus de 24h. est inférieur à 0 €.
</p> </p>
<p> <p>
@ -43,4 +43,4 @@
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p> </p>
</body> </body>
</html> </html>

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
@ -22,4 +22,4 @@ virement bancaire.
-- --
Le BDE Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

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

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

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

@ -36,7 +36,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir son compte utilisateur" "description": "Voir son compte utilisateur⋅rice"
} }
}, },
{ {
@ -68,7 +68,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir sa propre note d'utilisateur" "description": "Voir sa propre note d'utilisateur⋅rice"
} }
}, },
{ {
@ -116,7 +116,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir les aliases des notes des clubs et des adhérents du club BDE" "description": "Voir les alias des notes des clubs et des adhérent⋅es du club BDE"
} }
}, },
{ {
@ -772,7 +772,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir les adhérents du club" "description": "Voir les adhérent⋅es du club"
} }
}, },
{ {
@ -788,7 +788,7 @@
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un membre à un club" "description": "Ajouter un⋅e membre à un club"
} }
}, },
{ {
@ -852,7 +852,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Modifier n'importe quel utilisateur" "description": "Modifier n'importe quel⋅le utilisateur⋅rice"
} }
}, },
{ {
@ -868,7 +868,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un utilisateur" "description": "Ajouter un⋅e utilisateur⋅rice"
} }
}, },
{ {
@ -1284,7 +1284,7 @@
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Inscrire un 1A au WEI" "description": "Inscrire un⋅e 1A au WEI"
} }
}, },
{ {
@ -1956,7 +1956,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir mes activitées passées, même après la fin de l'adhésion BDE" "description": "Voir mes activités passées, même après la fin de l'adhésion BDE"
} }
}, },
{ {
@ -2100,7 +2100,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur" "description": "Voir n'importe quel⋅le utilisateur⋅rice"
} }
}, },
{ {
@ -2228,7 +2228,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une note d'utilisateur" "description": "Créer une note d'utilisateur⋅rice"
} }
}, },
{ {
@ -2276,7 +2276,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir tous les adhérents de tous les clubs" "description": "Voir toustes les adhérent⋅es de tous les clubs"
} }
}, },
{ {
@ -2292,7 +2292,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un membre à n'importe quel club" "description": "Ajouter un⋅e membre à n'importe quel club"
} }
}, },
{ {
@ -2372,7 +2372,7 @@
"mask": 1, "mask": 1,
"field": "name", "field": "name",
"permanent": false, "permanent": false,
"description": "Modifier le nom d'une activité non validée dont on est l'auteur" "description": "Modifier le nom d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2388,7 +2388,7 @@
"mask": 1, "mask": 1,
"field": "description", "field": "description",
"permanent": false, "permanent": false,
"description": "Modifier la description d'une activité non validée dont on est l'auteur" "description": "Modifier la description d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2404,7 +2404,7 @@
"mask": 1, "mask": 1,
"field": "location", "field": "location",
"permanent": false, "permanent": false,
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur" "description": "Modifier le lieu d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2420,7 +2420,7 @@
"mask": 1, "mask": 1,
"field": "activity_type", "field": "activity_type",
"permanent": false, "permanent": false,
"description": "Modifier le type d'une activité non validée dont on est l'auteur" "description": "Modifier le type d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2436,7 +2436,7 @@
"mask": 1, "mask": 1,
"field": "organizer", "field": "organizer",
"permanent": false, "permanent": false,
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur" "description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2452,7 +2452,7 @@
"mask": 1, "mask": 1,
"field": "attendees_club", "field": "attendees_club",
"permanent": false, "permanent": false,
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur" "description": "Modifier le club attendu d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2468,7 +2468,7 @@
"mask": 1, "mask": 1,
"field": "date_start", "field": "date_start",
"permanent": false, "permanent": false,
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur" "description": "Modifier la date de début d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2484,7 +2484,7 @@
"mask": 1, "mask": 1,
"field": "date_end", "field": "date_end",
"permanent": false, "permanent": false,
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur" "description": "Modifier la date de fin d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2591,12 +2591,12 @@
"note", "note",
"transaction" "transaction"
], ],
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]", "query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]",
"type": "add", "type": "add",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €" "description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€"
} }
}, },
{ {
@ -2756,7 +2756,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Modifier n'importe quel utilisateur non encore inscrit" "description": "Modifier n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
} }
}, },
{ {
@ -2788,7 +2788,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir tous les alias, y compris ceux des non adhérents" "description": "Voir tous les alias, y compris ceux des non adhérent⋅es"
} }
}, },
{ {
@ -2820,7 +2820,7 @@
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur non encore inscrit" "description": "Voir n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
} }
}, },
{ {
@ -2847,12 +2847,12 @@
"auth", "auth",
"user" "user"
], ],
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", "query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent⋅e BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view", "type": "view",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur qui est adhérent BDE" "description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e BDE"
} }
}, },
{ {
@ -3044,7 +3044,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir toutes les amitiés, y compris celles des non adhérents" "description": "Voir toutes les amitiés, y compris celles des non adhérent⋅es"
} }
}, },
{ {
@ -3116,7 +3116,7 @@
"pk": 1, "pk": 1,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Adh\u00e9rent BDE", "name": "Adh\u00e9rent\u22c5e BDE",
"permissions": [ "permissions": [
1, 1,
2, 2,
@ -3161,7 +3161,7 @@
"pk": 2, "pk": 2,
"fields": { "fields": {
"for_club": 2, "for_club": 2,
"name": "Adh\u00e9rent Kfet", "name": "Adh\u00e9rent\u22c5e Kfet",
"permissions": [ "permissions": [
22, 22,
36, 36,
@ -3225,10 +3225,11 @@
"pk": 5, "pk": 5,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Pr\u00e9sident\u00b7e de club", "name": "Pr\u00e9sident\u22c5e de club",
"permissions": [ "permissions": [
62, 62,
142 142,
135
] ]
} }
}, },
@ -3237,7 +3238,7 @@
"pk": 6, "pk": 6,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Tr\u00e9sorier\u00b7\u00e8re de club", "name": "Tr\u00e9sorièr\u22c5e de club",
"permissions": [ "permissions": [
19, 19,
20, 20,
@ -3261,7 +3262,7 @@
"pk": 7, "pk": 7,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Pr\u00e9sident\u00b7e BDE", "name": "Pr\u00e9sident\u22c5e BDE",
"permissions": [ "permissions": [
24, 24,
25, 25,
@ -3290,7 +3291,7 @@
"pk": 8, "pk": 8,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE", "name": "Tr\u00e9sorièr\u22c5e BDE",
"permissions": [ "permissions": [
23, 23,
24, 24,
@ -3458,7 +3459,7 @@
"pk": 13, "pk": 13,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Chef de bus", "name": "Chef\u22c5fe de bus",
"permissions": [ "permissions": [
22, 22,
84, 84,
@ -3477,7 +3478,7 @@
"pk": 14, "pk": 14,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Chef d'\u00e9quipe", "name": "Chef\u22c5fe d'\u00e9quipe",
"permissions": [ "permissions": [
22, 22,
84, 84,
@ -3526,7 +3527,7 @@
"pk": 18, "pk": 18,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Adhérent WEI", "name": "Adhérent\u22c5e WEI",
"permissions": [ "permissions": [
77, 77,
114 114

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
@ -36,8 +36,8 @@ class RightsTable(tables.Table):
def render_roles(self, record): def render_roles(self, record):
# If the user has the right to manage the roles, display the link to manage them # If the user has the right to manage the roles, display the link to manage them
roles = record.roles.filter((~(Q(name="Adhérent BDE") roles = record.roles.filter((~(Q(name="Adhérent⋅e BDE")
| Q(name="Adhérent Kfet") | Q(name="Adhérent⋅e Kfet")
| Q(name="Membre de club") | Q(name="Membre de club")
| Q(name="Bureau de club")) | Q(name="Bureau de club"))
& Q(weirole__isnull=True))).all() & Q(weirole__isnull=True))).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
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
@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase):
# Create membership to validate permissions # Create membership to validate permissions
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.save() membership.save()
# User is now a member and can now see its own user detail # User is now a member and can now see its own user detail
@ -85,7 +85,7 @@ class OAuth2TestCase(TestCase):
bde = Club.objects.get(name="BDE") bde = Club.objects.get(name="BDE")
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.save() membership.save()
resp = self.client.get(reverse('permission:scopes')) resp = self.client.get(reverse('permission:scopes'))

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
@ -131,8 +131,8 @@ class RightsView(TemplateView):
special_memberships = Membership.objects.filter( special_memberships = Membership.objects.filter(
date_start__lte=date.today(), date_start__lte=date.today(),
date_end__gte=date.today(), date_end__gte=date.today(),
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE") ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE")
| Q(name="Adhérent Kfet") | Q(name="Adhérent⋅e Kfet")
| Q(name="Membre de club") | Q(name="Membre de club")
| Q(name="Bureau de club")) | Q(name="Bureau de club"))
& Q(weirole__isnull=True))))\ & Q(weirole__isnull=True))))\

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