mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 07:49:57 +01:00 
			
		
		
		
	Compare commits
	
		
			177 Commits
		
	
	
		
			inclusive
			...
			2a288ba474
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2a288ba474 | ||
|  | 34646b8ddb | ||
|  | 2f23585252 | ||
|  | 201d6b114a | ||
|  | 19e77df299 | ||
|  | 5fd6ec5668 | ||
|  | 10a01c5bc2 | ||
|  | 989905ea64 | ||
|  | 0218d43a17 | ||
|  | 5d30b0e819 | ||
|  | ec759dd3c0 | ||
|  | 2eb965291d | ||
|  | 7f182ee2ee | ||
|  | 3132aa4c38 | ||
|  | c7eb774859 | ||
|  | 32f8d285b3 | ||
|  | 050256ea13 | ||
|  | 7afd15b1cc | ||
|  | 258361f116 | ||
|  | a307530579 | ||
|  | 5de930bf40 | ||
|  | f7ebe0e99b | ||
|  | 73de6e2176 | ||
|  | 201611b105 | ||
|  | 40c239e9da | ||
|  | 2aaab2b454 | ||
|  | fc088dec86 | ||
|  | 2d60f1fd7b | ||
|  | 7b48b09329 | ||
|  | ffac940511 | ||
|  | 50f98fd5ad | ||
|  | 402e19d1ce | ||
|  | 0b0394b61f | ||
|  | 98422d8259 | ||
|  | 29509b5b26 | ||
|  | 0d64ad31e0 | ||
|  | 5781cbd6a5 | ||
|  | 5295e61a00 | ||
|  | e79ed6226a | ||
|  | 68152e6354 | ||
|  | b8cc297baf | ||
|  | cd8224f2e0 | ||
|  | 3c882a7854 | ||
|  | 357e1bbaa2 | ||
|  | f5c4c58525 | ||
|  | dafb602b08 | ||
|  | 5b377e6a75 | ||
|  | 28bd62531e | ||
|  | b3a31c27a5 | ||
|  | c7a8e6a1a5 | ||
|  | 546a3a72b1 | ||
|  | 2e5664f79d | ||
|  | e367666fe9 | ||
|  | 04a9b3daf0 | ||
|  | d1df8f3eac | ||
|  | a5221f66ef | ||
|  | 7d59cd6cd2 | ||
|  | 0db0474217 | ||
|  | 2b3eb15f59 | ||
|  | a6b479db19 | ||
|  | 048d251f75 | ||
|  | 7b11cb0797 | ||
|  | ff3c30517e | ||
|  | f481ea6acb | ||
|  | 802fd8c2d7 | ||
|  | 5209a586a9 | ||
|  | 24f54ac876 | ||
|  | 988b4c9e88 | ||
|  | e32c267995 | ||
|  | 5e39209ab1 | ||
|  | 08b2fabe07 | ||
|  | 405479e5ad | ||
|  | 0cc130092f | ||
|  | ff6e207512 | ||
|  | 0f1e4d2e60 | ||
|  | 6255bcbbb1 | ||
|  | d82a1001c4 | ||
|  | 31a54482f0 | ||
|  | 4ee02345d4 | ||
|  | 422c087d17 | ||
|  | 30d6e2c95e | ||
|  | f3a3f07e38 | ||
|  | a5e802f370 | ||
|  | 540f3bc354 | ||
|  | 2d19457506 | ||
|  | 72786d0d2b | ||
|  | f099cbc879 | ||
|  | 977eb7c0d4 | ||
|  | d81b1f2710 | ||
|  | 6a69590a82 | ||
|  | 7afc583282 | ||
|  | 4fb0b7d736 | ||
|  | 18a5b65a1c | ||
|  | f545af4977 | ||
|  | 103e2d0635 | ||
|  | aedf0e87ba | ||
|  | dab45b5fd4 | ||
|  | b3353b563c | ||
|  | 6bc52be707 | ||
|  | 834d68fe35 | ||
|  | c6a2849d35 | ||
|  | 4ab22c92b3 | ||
|  | c328c1457c | ||
|  | 96da7d01ae | ||
|  | d27f942339 | ||
|  | 738d6c932d | ||
|  | 1760196578 | ||
|  | 13b9b6edea | ||
|  | e06e3b2972 | ||
|  | 9596aa7b8c | ||
|  | ba0d64f0d4 | ||
|  | 8d17801e28 | ||
|  | 609362c4f8 | ||
|  | 03d2d5f03e | ||
|  | d2057a9f45 | ||
|  | b6e68eeebe | ||
|  | 6410542027 | ||
|  | 6b1cd3ba7a | ||
|  | 9f114b8ca2 | ||
|  | e0132b6dc8 | ||
|  | f1cc82fab3 | ||
|  | 644cf14c4b | ||
|  | f19a489313 | ||
|  | dedd6c69cc | ||
|  | b42f5afeab | ||
|  | 31e67ae3f6 | ||
|  | b08da7a727 | ||
|  | 451aa64f33 | ||
|  | 3c99b0f3e9 | ||
|  | 201a179947 | ||
|  | 96784aee3b | ||
|  | 981c4d0300 | ||
|  | 11223430fd | ||
|  | 7aeb977e72 | ||
|  | 52fef1df42 | ||
|  | 16f8a60a3f | ||
|  | 2839d3de1e | ||
|  | 30afa6da0a | ||
|  | 84fc77696f | ||
|  | 19fc620d1f | ||
|  | d5819ac562 | ||
|  | a79df8f1f6 | ||
|  | 364b18e188 | ||
|  | 10a883b2e5 | ||
|  | 1410ab6c4f | ||
|  | 623dd61be6 | ||
|  | 48a0a87e7c | ||
|  | 563f525b11 | ||
|  | 63c1d74f1a | ||
|  | c42fb380a6 | ||
|  | c636d52a73 | ||
|  | 6a9021ec14 | ||
|  | 9c9149b53a | ||
|  | cb74311e7b | ||
|  | 9d7dd566c9 | ||
|  | 6bceb394c5 | ||
|  | 62cf8f9d84 | ||
|  | 9944ebcaad | ||
|  | 8537f043f7 | ||
|  | 2dd1c3fb89 | ||
|  | c8665c5798 | ||
|  | e9f1b6f52d | ||
|  | 1d95ae4810 | ||
|  | c89a95f8d2 | ||
|  | 73640b1dfa | ||
|  | 84b16ab603 | ||
|  | 6a1b51dbbf | ||
|  | c441a43a8b | ||
|  | 87f3b51b04 | ||
|  | 0a853fd3e6 | ||
|  | c429734810 | ||
|  | 5d759111b6 | ||
|  | 70baf7566c | ||
|  | eb355f547c | ||
|  | 7068170f18 | ||
|  | 45ee9a8941 | ||
|  | 454ea19603 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -42,6 +42,7 @@ map.json | |||||||
| backups/ | backups/ | ||||||
| /static/ | /static/ | ||||||
| /media/ | /media/ | ||||||
|  | /tmp/ | ||||||
|  |  | ||||||
| # Virtualenv | # Virtualenv | ||||||
| env/ | env/ | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
|             "name": "Pot", |             "name": "Pot", | ||||||
|             "manage_entries": true, |             "manage_entries": true, | ||||||
|             "can_invite": true, |             "can_invite": true, | ||||||
|             "guest_entry_fee": 500 |             "guest_entry_fee": 1000 | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
| @@ -28,5 +28,25 @@ | |||||||
|             "can_invite": false, |             "can_invite": false, | ||||||
|             "guest_entry_fee": 0 |             "guest_entry_fee": 0 | ||||||
|         } |         } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         "model": "activity.activitytype", | ||||||
|  |         "pk": 5, | ||||||
|  |         "fields": { | ||||||
|  |             "name": "Soir\u00e9e avec entrées", | ||||||
|  |             "manage_entries": true, | ||||||
|  |             "can_invite": false, | ||||||
|  |             "guest_entry_fee": 0 | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         "model": "activity.activitytype", | ||||||
|  |         "pk": 7, | ||||||
|  |         "fields": { | ||||||
|  |             "name": "Soir\u00e9e avec invitations", | ||||||
|  |             "manage_entries": true, | ||||||
|  |             "can_invite": true, | ||||||
|  |             "guest_entry_fee": 0 | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								apps/activity/migrations/0003_auto_20240323_1422.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/activity/migrations/0003_auto_20240323_1422.py
									
									
									
									
									
										Normal 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'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 %} | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from hashlib import md5 | from hashlib import md5 | ||||||
| @@ -76,6 +76,7 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView | |||||||
|         context['upcoming'] = ActivityTable( |         context['upcoming'] = ActivityTable( | ||||||
|             data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), |             data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), | ||||||
|             prefix='upcoming-', |             prefix='upcoming-', | ||||||
|  |             order_by='date_start', | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() |         started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								apps/api/pagination.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								apps/api/pagination.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | from rest_framework.pagination import PageNumberPagination | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CustomPagination(PageNumberPagination): | ||||||
|  |     page_size_query_param = 'page_size' | ||||||
| @@ -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 | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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.")) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
| @@ -47,6 +47,13 @@ class ProfileForm(forms.ModelForm): | |||||||
|  |  | ||||||
|     last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) |     last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) | ||||||
|  |  | ||||||
|  |     VSS_charter_read = forms.BooleanField( | ||||||
|  |         required=True, | ||||||
|  |         label=_("Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"), | ||||||
|  |         help_text=_("Tick after having read and accepted the anti-VSS charter \ | ||||||
|  |         <a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> available here in pdf</a>") | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     def clean_promotion(self): |     def clean_promotion(self): | ||||||
|         promotion = self.cleaned_data["promotion"] |         promotion = self.cleaned_data["promotion"] | ||||||
|         if promotion > timezone.now().year: |         if promotion > timezone.now().year: | ||||||
| @@ -114,7 +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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								apps/member/migrations/0009_auto_20220904_2325.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/migrations/0009_auto_20220904_2325.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 2.2.26 on 2022-09-04 21:25 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('member', '0008_auto_20211005_1544'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='profile', | ||||||
|  |             name='promotion', | ||||||
|  |             field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								apps/member/migrations/0010_new_default_year.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/migrations/0010_new_default_year.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 2.2.28 on 2023-08-23 21:29 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('member', '0009_auto_20220904_2325'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='profile', | ||||||
|  |             name='promotion', | ||||||
|  |             field=models.PositiveSmallIntegerField(default=2023, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								apps/member/migrations/0011_profile_vss_charter_read.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/migrations/0011_profile_vss_charter_read.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 2.2.28 on 2023-08-31 09:50 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('member', '0010_new_default_year'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='profile', | ||||||
|  |             name='VSS_charter_read', | ||||||
|  |             field=models.BooleanField(default=False, verbose_name='VSS charter read'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| import datetime | import datetime | ||||||
| @@ -28,7 +28,6 @@ class Profile(models.Model): | |||||||
|     We do not want to patch the Django Contrib :model:`auth.User`model; |     We do not want to patch the Django Contrib :model:`auth.User`model; | ||||||
|     so this model add an user profile with additional information. |     so this model add an user profile with additional information. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     user = models.OneToOneField( |     user = models.OneToOneField( | ||||||
|         settings.AUTH_USER_MODEL, |         settings.AUTH_USER_MODEL, | ||||||
|         on_delete=models.CASCADE, |         on_delete=models.CASCADE, | ||||||
| @@ -134,6 +133,22 @@ class Profile(models.Model): | |||||||
|         default=False, |         default=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     VSS_charter_read = models.BooleanField( | ||||||
|  |         verbose_name=_("VSS charter read"), | ||||||
|  |         default=False | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _('user profile') | ||||||
|  |         verbose_name_plural = _('user profile') | ||||||
|  |         indexes = [models.Index(fields=['user'])] | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return str(self.user) | ||||||
|  |  | ||||||
|  |     def get_absolute_url(self): | ||||||
|  |         return reverse('member:user_detail', args=(self.user_id,)) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def ens_year(self): |     def ens_year(self): | ||||||
|         """ |         """ | ||||||
| @@ -158,17 +173,6 @@ class Profile(models.Model): | |||||||
|             return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() |             return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _('user profile') |  | ||||||
|         verbose_name_plural = _('user profile') |  | ||||||
|         indexes = [models.Index(fields=['user'])] |  | ||||||
|  |  | ||||||
|     def get_absolute_url(self): |  | ||||||
|         return reverse('member:user_detail', args=(self.user_id,)) |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return str(self.user) |  | ||||||
|  |  | ||||||
|     def send_email_validation_link(self): |     def send_email_validation_link(self): | ||||||
|         subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) |         subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) | ||||||
|         token = email_validation_token.make_token(self.user) |         token = email_validation_token.make_token(self.user) | ||||||
| @@ -200,9 +204,11 @@ class Club(models.Model): | |||||||
|         max_length=255, |         max_length=255, | ||||||
|         unique=True, |         unique=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     email = models.EmailField( |     email = models.EmailField( | ||||||
|         verbose_name=_('email'), |         verbose_name=_('email'), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     parent_club = models.ForeignKey( |     parent_club = models.ForeignKey( | ||||||
|         'self', |         'self', | ||||||
|         null=True, |         null=True, | ||||||
| @@ -253,25 +259,12 @@ class Club(models.Model): | |||||||
|         help_text=_('Maximal date of a membership, after which members must renew it.'), |         help_text=_('Maximal date of a membership, after which members must renew it.'), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     def update_membership_dates(self): |     class Meta: | ||||||
|         """ |         verbose_name = _("club") | ||||||
|         This function is called each time the club detail view is displayed. |         verbose_name_plural = _("clubs") | ||||||
|         Update the year of the membership dates. |  | ||||||
|         """ |  | ||||||
|         if not self.membership_start or not self.membership_end: |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         today = datetime.date.today() |     def __str__(self): | ||||||
|  |         return self.name | ||||||
|         if (today - self.membership_start).days >= 365: |  | ||||||
|             if self.membership_start: |  | ||||||
|                 self.membership_start = datetime.date(self.membership_start.year + 1, |  | ||||||
|                                                       self.membership_start.month, self.membership_start.day) |  | ||||||
|             if self.membership_end: |  | ||||||
|                 self.membership_end = datetime.date(self.membership_end.year + 1, |  | ||||||
|                                                     self.membership_end.month, self.membership_end.day) |  | ||||||
|             self._force_save = True |  | ||||||
|             self.save(force_update=True) |  | ||||||
|  |  | ||||||
|     @transaction.atomic |     @transaction.atomic | ||||||
|     def save(self, force_insert=False, force_update=False, using=None, |     def save(self, force_insert=False, force_update=False, using=None, | ||||||
| @@ -284,16 +277,29 @@ class Club(models.Model): | |||||||
|             self.membership_end = None |             self.membership_end = None | ||||||
|         super().save(force_insert, force_update, update_fields) |         super().save(force_insert, force_update, update_fields) | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("club") |  | ||||||
|         verbose_name_plural = _("clubs") |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.name |  | ||||||
|  |  | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         return reverse_lazy('member:club_detail', args=(self.pk,)) |         return reverse_lazy('member:club_detail', args=(self.pk,)) | ||||||
|  |  | ||||||
|  |     def update_membership_dates(self): | ||||||
|  |         """ | ||||||
|  |         This function is called each time the club detail view is displayed. | ||||||
|  |         Update the year of the membership dates. | ||||||
|  |         """ | ||||||
|  |         if not self.membership_start or not self.membership_end: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         today = datetime.date.today() | ||||||
|  |  | ||||||
|  |         while (today - self.membership_start).days >= 365: | ||||||
|  |             if self.membership_start: | ||||||
|  |                 self.membership_start = datetime.date(self.membership_start.year + 1, | ||||||
|  |                                                       self.membership_start.month, self.membership_start.day) | ||||||
|  |             if self.membership_end: | ||||||
|  |                 self.membership_end = datetime.date(self.membership_end.year + 1, | ||||||
|  |                                                     self.membership_end.month, self.membership_end.day) | ||||||
|  |             self._force_save = True | ||||||
|  |             self.save(force_update=True) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Membership(models.Model): | class Membership(models.Model): | ||||||
|     """ |     """ | ||||||
| @@ -333,6 +339,66 @@ class Membership(models.Model): | |||||||
|         verbose_name=_('fee'), |         verbose_name=_('fee'), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _('membership') | ||||||
|  |         verbose_name_plural = _('memberships') | ||||||
|  |         indexes = [models.Index(fields=['user'])] | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, ) | ||||||
|  |  | ||||||
|  |     @transaction.atomic | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         Calculate fee and end date before saving the membership and creating the transaction if needed. | ||||||
|  |         """ | ||||||
|  |         # Ensure that club membership dates are valid | ||||||
|  |         old_membership_start = self.club.membership_start | ||||||
|  |         self.club.update_membership_dates() | ||||||
|  |         if self.club.membership_start != old_membership_start: | ||||||
|  |             self.club.save() | ||||||
|  |  | ||||||
|  |         created = not self.pk | ||||||
|  |         if not created: | ||||||
|  |             for role in self.roles.all(): | ||||||
|  |                 club = role.for_club | ||||||
|  |                 if club is not None: | ||||||
|  |                     if club.pk != self.club_id: | ||||||
|  |                         raise ValidationError(_('The role {role} does not apply to the club {club}.') | ||||||
|  |                                               .format(role=role.name, club=club.name)) | ||||||
|  |         else: | ||||||
|  |             if Membership.objects.filter( | ||||||
|  |                     user=self.user, | ||||||
|  |                     club=self.club, | ||||||
|  |                     date_start__lte=self.date_start, | ||||||
|  |                     date_end__gte=self.date_start, | ||||||
|  |             ).exists(): | ||||||
|  |                 raise ValidationError(_('User is already a member of the club')) | ||||||
|  |  | ||||||
|  |             if self.club.parent_club is not None: | ||||||
|  |                 # Check that the user is already a member of the parent club if the membership is created | ||||||
|  |                 if not Membership.objects.filter( | ||||||
|  |                     user=self.user, | ||||||
|  |                     club=self.club.parent_club, | ||||||
|  |                     date_start__gte=self.club.parent_club.membership_start, | ||||||
|  |                 ).exists(): | ||||||
|  |                     if hasattr(self, '_force_renew_parent') and self._force_renew_parent: | ||||||
|  |                         self.renew_parent() | ||||||
|  |                     else: | ||||||
|  |                         raise ValidationError(_('User is not a member of the parent club') | ||||||
|  |                                               + ' ' + self.club.parent_club.name) | ||||||
|  |  | ||||||
|  |             self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid | ||||||
|  |  | ||||||
|  |             self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \ | ||||||
|  |                 if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242) | ||||||
|  |             if self.club.membership_end is not None and self.date_end > self.club.membership_end: | ||||||
|  |                 self.date_end = self.club.membership_end | ||||||
|  |  | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         self.make_transaction() | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def valid(self): |     def valid(self): | ||||||
|         """ |         """ | ||||||
| @@ -410,58 +476,6 @@ class Membership(models.Model): | |||||||
|                 parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) |                 parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) | ||||||
|             parent_membership.save() |             parent_membership.save() | ||||||
|  |  | ||||||
|     @transaction.atomic |  | ||||||
|     def save(self, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Calculate fee and end date before saving the membership and creating the transaction if needed. |  | ||||||
|         """ |  | ||||||
|         # Ensure that club membership dates are valid |  | ||||||
|         old_membership_start = self.club.membership_start |  | ||||||
|         self.club.update_membership_dates() |  | ||||||
|         if self.club.membership_start != old_membership_start: |  | ||||||
|             self.club.save() |  | ||||||
|  |  | ||||||
|         created = not self.pk |  | ||||||
|         if not created: |  | ||||||
|             for role in self.roles.all(): |  | ||||||
|                 club = role.for_club |  | ||||||
|                 if club is not None: |  | ||||||
|                     if club.pk != self.club_id: |  | ||||||
|                         raise ValidationError(_('The role {role} does not apply to the club {club}.') |  | ||||||
|                                               .format(role=role.name, club=club.name)) |  | ||||||
|         else: |  | ||||||
|             if Membership.objects.filter( |  | ||||||
|                     user=self.user, |  | ||||||
|                     club=self.club, |  | ||||||
|                     date_start__lte=self.date_start, |  | ||||||
|                     date_end__gte=self.date_start, |  | ||||||
|             ).exists(): |  | ||||||
|                 raise ValidationError(_('User is already a member of the club')) |  | ||||||
|  |  | ||||||
|             if self.club.parent_club is not None: |  | ||||||
|                 # Check that the user is already a member of the parent club if the membership is created |  | ||||||
|                 if not Membership.objects.filter( |  | ||||||
|                     user=self.user, |  | ||||||
|                     club=self.club.parent_club, |  | ||||||
|                     date_start__gte=self.club.parent_club.membership_start, |  | ||||||
|                 ).exists(): |  | ||||||
|                     if hasattr(self, '_force_renew_parent') and self._force_renew_parent: |  | ||||||
|                         self.renew_parent() |  | ||||||
|                     else: |  | ||||||
|                         raise ValidationError(_('User is not a member of the parent club') |  | ||||||
|                                               + ' ' + self.club.parent_club.name) |  | ||||||
|  |  | ||||||
|             self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid |  | ||||||
|  |  | ||||||
|             self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \ |  | ||||||
|                 if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242) |  | ||||||
|             if self.club.membership_end is not None and self.date_end > self.club.membership_end: |  | ||||||
|                 self.date_end = self.club.membership_end |  | ||||||
|  |  | ||||||
|         super().save(*args, **kwargs) |  | ||||||
|  |  | ||||||
|         self.make_transaction() |  | ||||||
|  |  | ||||||
|     def make_transaction(self): |     def make_transaction(self): | ||||||
|         """ |         """ | ||||||
|         Create Membership transaction associated to this membership. |         Create Membership transaction associated to this membership. | ||||||
| @@ -499,11 +513,3 @@ class Membership(models.Model): | |||||||
|                 soge_credit.save() |                 soge_credit.save() | ||||||
|             else: |             else: | ||||||
|                 transaction.save(force_insert=True) |                 transaction.save(force_insert=True) | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, ) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _('membership') |  | ||||||
|         verbose_name_plural = _('memberships') |  | ||||||
|         indexes = [models.Index(fields=['user'])] |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| /** | /** | ||||||
|  * On form submit, create a new friendship |  * On form submit, create a new friendship | ||||||
|  */ |  */ | ||||||
| function create_trust (e) { | function form_create_trust (e) { | ||||||
|   // Do not submit HTML form |   // Do not submit HTML form | ||||||
|   e.preventDefault() |   e.preventDefault() | ||||||
|  |  | ||||||
| @@ -14,25 +14,35 @@ function create_trust (e) { | |||||||
|          addMsg(gettext("You can't add yourself as a friend"), "danger") |          addMsg(gettext("You can't add yourself as a friend"), "danger") | ||||||
|          return |          return | ||||||
|       } |       } | ||||||
|       $.post('/api/note/trust/', { |       create_trust(formData.get('trusting'), trusted_alias.note) | ||||||
|         csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'), |  | ||||||
|         trusting: formData.get('trusting'), |  | ||||||
|         trusted: trusted_alias.note |  | ||||||
|       }).done(function () { |  | ||||||
|         // Reload table |  | ||||||
|         $('#trust_table').load(location.pathname + ' #trust_table') |  | ||||||
|         addMsg(gettext('Friendship successfully added'), 'success') |  | ||||||
|       }).fail(function (xhr, _textStatus, _error) { |  | ||||||
|         errMsg(xhr.responseJSON) |  | ||||||
|       }) |  | ||||||
|     }).fail(function (xhr, _textStatus, _error) { |     }).fail(function (xhr, _textStatus, _error) { | ||||||
|         errMsg(xhr.responseJSON) |         errMsg(xhr.responseJSON) | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * On click of "delete", delete the alias |  * Create a trust between users | ||||||
|  * @param button_id:Integer Alias id to remove |  * @param trusting:Integer trusting note id | ||||||
|  |  * @param trusted:Integer trusted note id | ||||||
|  |  */ | ||||||
|  | function create_trust(trusting, trusted) { | ||||||
|  |   $.post('/api/note/trust/', { | ||||||
|  |       trusting: trusting, | ||||||
|  |       trusted: trusted, | ||||||
|  |       csrfmiddlewaretoken: CSRF_TOKEN | ||||||
|  |   }).done(function () { | ||||||
|  |   // Reload tables | ||||||
|  |   $('#trust_table').load(location.pathname + ' #trust_table') | ||||||
|  |   $('#trusted_table').load(location.pathname + ' #trusted_table') | ||||||
|  |     addMsg(gettext('Friendship successfully added'), 'success') | ||||||
|  |   }).fail(function (xhr, _textStatus, _error) { | ||||||
|  |     errMsg(xhr.responseJSON) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * On click of "delete", delete the trust | ||||||
|  |  * @param button_id:Integer Trust id to remove | ||||||
|  */ |  */ | ||||||
| function delete_button (button_id) { | function delete_button (button_id) { | ||||||
|   $.ajax({ |   $.ajax({ | ||||||
| @@ -42,6 +52,7 @@ function delete_button (button_id) { | |||||||
|   }).done(function () { |   }).done(function () { | ||||||
|     addMsg(gettext('Friendship successfully deleted'), 'success') |     addMsg(gettext('Friendship successfully deleted'), 'success') | ||||||
|     $('#trust_table').load(location.pathname + ' #trust_table') |     $('#trust_table').load(location.pathname + ' #trust_table') | ||||||
|  |     $('#trusted_table').load(location.pathname + ' #trusted_table') | ||||||
|   }).fail(function (xhr, _textStatus, _error) { |   }).fail(function (xhr, _textStatus, _error) { | ||||||
|     errMsg(xhr.responseJSON) |     errMsg(xhr.responseJSON) | ||||||
|   }) |   }) | ||||||
| @@ -49,5 +60,5 @@ function delete_button (button_id) { | |||||||
|  |  | ||||||
| $(document).ready(function () { | $(document).ready(function () { | ||||||
|   // Attach event |   // Attach event | ||||||
|   document.getElementById('form_trust').addEventListener('submit', create_trust) |   document.getElementById('form_trust').addEventListener('submit', form_create_trust) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
| {% block profile_content %} | {% block profile_content %} | ||||||
| <div class="card bg-light mb-3"> | <div class="card bg-light mb-3"> | ||||||
|     <h3 class="card-header text-center"> |     <h3 class="card-header text-center"> | ||||||
|         {% trans "Note friendships" %} |         {% trans "Add friends" %} | ||||||
|     </h3> |     </h3> | ||||||
|     <div class="card-body"> |     <div class="card-body"> | ||||||
|         {% if can_create %} |         {% if can_create %} | ||||||
| @@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|     {% render_table trusting %} |     {% render_table trusting %} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="alert alert-warning card"> | <div class="alert alert-warning card mb-3"> | ||||||
|     {% blocktrans trimmed %} |     {% blocktrans trimmed %} | ||||||
|         Adding someone as a friend enables them to initiate transactions coming |         Adding someone as a friend enables them to initiate transactions coming | ||||||
|         from your account (while keeping your balance positive). This is |         from your account (while keeping your balance positive). This is | ||||||
| @@ -33,6 +33,13 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|         friends without needing additional rights among them. |         friends without needing additional rights among them. | ||||||
|     {% endblocktrans %} |     {% endblocktrans %} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | <div class="card bg-light mb-3"> | ||||||
|  |     <h3 class="card-header text-center"> | ||||||
|  |         {% trans "People having you as a friend" %} | ||||||
|  |     </h3> | ||||||
|  |     {% render_table trusted_by %} | ||||||
|  | </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block extrajavascript %} | {% block extrajavascript %} | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -183,7 +183,7 @@ class TestMemberships(TestCase): | |||||||
|                 club = Club.objects.get(name="Kfet") |                 club = Club.objects.get(name="Kfet") | ||||||
|             else: |             else: | ||||||
|                 club = Club.objects.create( |                 club = Club.objects.create( | ||||||
|                     name="Second club " + ("with BDE" if bde_parent else "without BDE"), |                     name="Second club without BDE", | ||||||
|                     parent_club=None, |                     parent_club=None, | ||||||
|                     email="newclub@example.com", |                     email="newclub@example.com", | ||||||
|                     require_memberships=True, |                     require_memberships=True, | ||||||
| @@ -335,6 +335,7 @@ class TestMemberships(TestCase): | |||||||
|             ml_sports_registration=True, |             ml_sports_registration=True, | ||||||
|             ml_art_registration=True, |             ml_art_registration=True, | ||||||
|             report_frequency=7, |             report_frequency=7, | ||||||
|  |             VSS_charter_read=True | ||||||
|         )) |         )) | ||||||
|         self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) |         self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) | ||||||
|         self.assertTrue(User.objects.filter(username="toto changed").exists()) |         self.assertTrue(User.objects.filter(username="toto changed").exists()) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from datetime import timedelta, date | from datetime import timedelta, date | ||||||
| @@ -8,7 +8,6 @@ from django.contrib.auth import logout | |||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| from django.contrib.auth.views import LoginView | from django.contrib.auth.views import LoginView | ||||||
| from django.contrib.contenttypes.models import ContentType |  | ||||||
| from django.db import transaction | from django.db import transaction | ||||||
| from django.db.models import Q, F | from django.db.models import Q, F | ||||||
| from django.shortcuts import redirect | from django.shortcuts import redirect | ||||||
| @@ -21,13 +20,13 @@ from django_tables2.views import SingleTableView | |||||||
| from rest_framework.authtoken.models import Token | from rest_framework.authtoken.models import Token | ||||||
| from note.models import Alias, NoteClub, NoteUser, Trust | from note.models import Alias, NoteClub, NoteUser, Trust | ||||||
| from note.models.transactions import Transaction, SpecialTransaction | from note.models.transactions import Transaction, SpecialTransaction | ||||||
| from note.tables import HistoryTable, AliasTable, TrustTable | from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable | ||||||
| from note_kfet.middlewares import _set_current_request | from note_kfet.middlewares import _set_current_request | ||||||
| from permission.backends import PermissionBackend | from permission.backends import PermissionBackend | ||||||
| from permission.models import Role | from permission.models import Role | ||||||
| from permission.views import ProtectQuerysetMixin, ProtectedCreateView | from permission.views import ProtectQuerysetMixin, ProtectedCreateView | ||||||
|  |  | ||||||
| from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ | from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ | ||||||
|     CustomAuthenticationForm, MembershipRolesForm |     CustomAuthenticationForm, MembershipRolesForm | ||||||
| from .models import Club, Membership | from .models import Club, Membership | ||||||
| from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable | from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable | ||||||
| @@ -258,17 +257,18 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): | |||||||
|         note = context['object'].note |         note = context['object'].note | ||||||
|         context["trusting"] = TrustTable( |         context["trusting"] = TrustTable( | ||||||
|             note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) |             note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) | ||||||
|  |         context["trusted_by"] = TrustedTable( | ||||||
|  |             note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) | ||||||
|         context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust( |         context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust( | ||||||
|             trusting=context["object"].note, |             trusting=context["object"].note, | ||||||
|             trusted=context["object"].note |             trusted=context["object"].note | ||||||
|         )) |         )) | ||||||
|         context["widget"] = { |         context["widget"] = { | ||||||
|             "name": "trusted", |             "name": "trusted", | ||||||
|  |             "resetable": True, | ||||||
|             "attrs": { |             "attrs": { | ||||||
|                 "model_pk": ContentType.objects.get_for_model(Alias).pk, |  | ||||||
|                 "class": "autocomplete form-control", |                 "class": "autocomplete form-control", | ||||||
|                 "id": "trusted", |                 "id": "trusted", | ||||||
|                 "resetable": True, |  | ||||||
|                 "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", |                 "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", | ||||||
|                 "name_field": "name", |                 "name_field": "name", | ||||||
|                 "placeholder": "" |                 "placeholder": "" | ||||||
| @@ -753,6 +753,10 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): | |||||||
|             club = old_membership.club |             club = old_membership.club | ||||||
|             user = old_membership.user |             user = old_membership.user | ||||||
|  |  | ||||||
|  |         # Update club membership date | ||||||
|  |         if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club): | ||||||
|  |             club.update_membership_dates() | ||||||
|  |  | ||||||
|         form.instance.club = club |         form.instance.club = club | ||||||
|  |  | ||||||
|         # Get form data |         # Get form data | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| @@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \ | |||||||
|     PolymorphicChildModelFilter, PolymorphicParentModelAdmin |     PolymorphicChildModelFilter, PolymorphicParentModelAdmin | ||||||
| from note_kfet.admin import admin_site | from note_kfet.admin import admin_site | ||||||
|  |  | ||||||
| from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser | from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust | ||||||
| from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ | from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ | ||||||
|     RecurrentTransaction, MembershipTransaction, SpecialTransaction |     RecurrentTransaction, MembershipTransaction, SpecialTransaction | ||||||
| from .templatetags.pretty_money import pretty_money | from .templatetags.pretty_money import pretty_money | ||||||
| @@ -21,6 +21,16 @@ class AliasInlines(admin.TabularInline): | |||||||
|     model = Alias |     model = Alias | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TrustInlines(admin.TabularInline): | ||||||
|  |     """ | ||||||
|  |     Define trusts when editing the trusting note | ||||||
|  |     """ | ||||||
|  |     model = Trust | ||||||
|  |     fk_name = "trusting" | ||||||
|  |     extra = 0 | ||||||
|  |     readonly_fields = ("trusted",) | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Note, site=admin_site) | @admin.register(Note, site=admin_site) | ||||||
| class NoteAdmin(PolymorphicParentModelAdmin): | class NoteAdmin(PolymorphicParentModelAdmin): | ||||||
|     """ |     """ | ||||||
| @@ -92,7 +102,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin): | |||||||
|     """ |     """ | ||||||
|     Child for an user note, see NoteAdmin |     Child for an user note, see NoteAdmin | ||||||
|     """ |     """ | ||||||
|     inlines = (AliasInlines,) |     inlines = (AliasInlines, TrustInlines) | ||||||
|  |  | ||||||
|     # We can't change user after creation or the balance |     # We can't change user after creation or the balance | ||||||
|     readonly_fields = ('user', 'balance') |     readonly_fields = ('user', 'balance') | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| @@ -11,6 +11,7 @@ from member.models import Membership | |||||||
| from note_kfet.middlewares import get_current_request | from note_kfet.middlewares import get_current_request | ||||||
| from permission.backends import PermissionBackend | from permission.backends import PermissionBackend | ||||||
| from rest_framework.utils import model_meta | from rest_framework.utils import model_meta | ||||||
|  | from rest_framework.validators import UniqueTogetherValidator | ||||||
|  |  | ||||||
| from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust | from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust | ||||||
| from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ | from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ | ||||||
| @@ -86,11 +87,9 @@ class TrustSerializer(serializers.ModelSerializer): | |||||||
|     class Meta: |     class Meta: | ||||||
|         model = Trust |         model = Trust | ||||||
|         fields = '__all__' |         fields = '__all__' | ||||||
|  |         validators = [UniqueTogetherValidator( | ||||||
|     def validate(self, attrs): |             queryset=Trust.objects.all(), fields=('trusting', 'trusted'), | ||||||
|         instance = Trust(**attrs) |             message=_("This friendship already exists"))] | ||||||
|         instance.clean() |  | ||||||
|         return attrs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AliasSerializer(serializers.ModelSerializer): | class AliasSerializer(serializers.ModelSerializer): | ||||||
|   | |||||||
| @@ -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, \ | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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."), | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| @@ -59,6 +59,7 @@ class TransactionTemplate(models.Model): | |||||||
|     amount = models.PositiveIntegerField( |     amount = models.PositiveIntegerField( | ||||||
|         verbose_name=_('amount'), |         verbose_name=_('amount'), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     category = models.ForeignKey( |     category = models.ForeignKey( | ||||||
|         TemplateCategory, |         TemplateCategory, | ||||||
|         on_delete=models.PROTECT, |         on_delete=models.PROTECT, | ||||||
| @@ -87,12 +88,12 @@ class TransactionTemplate(models.Model): | |||||||
|         verbose_name = _("transaction template") |         verbose_name = _("transaction template") | ||||||
|         verbose_name_plural = _("transaction templates") |         verbose_name_plural = _("transaction templates") | ||||||
|  |  | ||||||
|     def get_absolute_url(self): |  | ||||||
|         return reverse('note:template_update', args=(self.pk,)) |  | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
|  |     def get_absolute_url(self): | ||||||
|  |         return reverse('note:template_update', args=(self.pk,)) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Transaction(PolymorphicModel): | class Transaction(PolymorphicModel): | ||||||
|     """ |     """ | ||||||
| @@ -101,7 +102,6 @@ class Transaction(PolymorphicModel): | |||||||
|     amount is store in centimes of currency, making it a  positive integer |     amount is store in centimes of currency, making it a  positive integer | ||||||
|     value. (from someone to someone else) |     value. (from someone to someone else) | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     source = models.ForeignKey( |     source = models.ForeignKey( | ||||||
|         Note, |         Note, | ||||||
|         on_delete=models.PROTECT, |         on_delete=models.PROTECT, | ||||||
| @@ -166,6 +166,50 @@ class Transaction(PolymorphicModel): | |||||||
|             models.Index(fields=['destination']), |             models.Index(fields=['destination']), | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\ | ||||||
|  |             + pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid") | ||||||
|  |  | ||||||
|  |     @transaction.atomic | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         When saving, also transfer money between two notes | ||||||
|  |         """ | ||||||
|  |         if self.source.pk == self.destination.pk: | ||||||
|  |             # When source == destination, no money is transferred and no transaction is created | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         self.source = Note.objects.select_for_update().get(pk=self.source_id) | ||||||
|  |         self.destination = Note.objects.select_for_update().get(pk=self.destination_id) | ||||||
|  |  | ||||||
|  |         # Check that the amounts stay between big integer bounds | ||||||
|  |         diff_source, diff_dest = self.validate() | ||||||
|  |  | ||||||
|  |         if not (hasattr(self, '_force_save') and self._force_save) \ | ||||||
|  |                 and (not self.source.is_active or not self.destination.is_active): | ||||||
|  |             raise ValidationError(_("The transaction can't be saved since the source note " | ||||||
|  |                                     "or the destination note is not active.")) | ||||||
|  |  | ||||||
|  |         # If the aliases are not entered, we assume that the used alias is the name of the note | ||||||
|  |         if not self.source_alias: | ||||||
|  |             self.source_alias = str(self.source) | ||||||
|  |  | ||||||
|  |         if not self.destination_alias: | ||||||
|  |             self.destination_alias = str(self.destination) | ||||||
|  |  | ||||||
|  |         # We save first the transaction, in case of the user has no right to transfer money | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         # Save notes | ||||||
|  |         self.source.refresh_from_db() | ||||||
|  |         self.source.balance += diff_source | ||||||
|  |         self.source._force_save = True | ||||||
|  |         self.source.save() | ||||||
|  |         self.destination.refresh_from_db() | ||||||
|  |         self.destination.balance += diff_dest | ||||||
|  |         self.destination._force_save = True | ||||||
|  |         self.destination.save() | ||||||
|  |  | ||||||
|     def validate(self): |     def validate(self): | ||||||
|         previous_source_balance = self.source.balance |         previous_source_balance = self.source.balance | ||||||
|         previous_dest_balance = self.destination.balance |         previous_dest_balance = self.destination.balance | ||||||
| @@ -208,46 +252,6 @@ class Transaction(PolymorphicModel): | |||||||
|  |  | ||||||
|         return source_balance - previous_source_balance, dest_balance - previous_dest_balance |         return source_balance - previous_source_balance, dest_balance - previous_dest_balance | ||||||
|  |  | ||||||
|     @transaction.atomic |  | ||||||
|     def save(self, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         When saving, also transfer money between two notes |  | ||||||
|         """ |  | ||||||
|         if self.source.pk == self.destination.pk: |  | ||||||
|             # When source == destination, no money is transferred and no transaction is created |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         self.source = Note.objects.select_for_update().get(pk=self.source_id) |  | ||||||
|         self.destination = Note.objects.select_for_update().get(pk=self.destination_id) |  | ||||||
|  |  | ||||||
|         # Check that the amounts stay between big integer bounds |  | ||||||
|         diff_source, diff_dest = self.validate() |  | ||||||
|  |  | ||||||
|         if not (hasattr(self, '_force_save') and self._force_save) \ |  | ||||||
|                 and (not self.source.is_active or not self.destination.is_active): |  | ||||||
|             raise ValidationError(_("The transaction can't be saved since the source note " |  | ||||||
|                                     "or the destination note is not active.")) |  | ||||||
|  |  | ||||||
|         # If the aliases are not entered, we assume that the used alias is the name of the note |  | ||||||
|         if not self.source_alias: |  | ||||||
|             self.source_alias = str(self.source) |  | ||||||
|  |  | ||||||
|         if not self.destination_alias: |  | ||||||
|             self.destination_alias = str(self.destination) |  | ||||||
|  |  | ||||||
|         # We save first the transaction, in case of the user has no right to transfer money |  | ||||||
|         super().save(*args, **kwargs) |  | ||||||
|  |  | ||||||
|         # Save notes |  | ||||||
|         self.source.refresh_from_db() |  | ||||||
|         self.source.balance += diff_source |  | ||||||
|         self.source._force_save = True |  | ||||||
|         self.source.save() |  | ||||||
|         self.destination.refresh_from_db() |  | ||||||
|         self.destination.balance += diff_dest |  | ||||||
|         self.destination._force_save = True |  | ||||||
|         self.destination.save() |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def total(self): |     def total(self): | ||||||
|         return self.amount * self.quantity |         return self.amount * self.quantity | ||||||
| @@ -256,46 +260,40 @@ class Transaction(PolymorphicModel): | |||||||
|     def type(self): |     def type(self): | ||||||
|         return _('Transfer') |         return _('Transfer') | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\ |  | ||||||
|             + pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RecurrentTransaction(Transaction): | class RecurrentTransaction(Transaction): | ||||||
|     """ |     """ | ||||||
|     Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`. |     Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     template = models.ForeignKey( |     template = models.ForeignKey( | ||||||
|         TransactionTemplate, |         TransactionTemplate, | ||||||
|         on_delete=models.PROTECT, |         on_delete=models.PROTECT, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("recurrent transaction") | ||||||
|  |         verbose_name_plural = _("recurrent transactions") | ||||||
|  |  | ||||||
|  |     @transaction.atomic | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         self.clean() | ||||||
|  |         return super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|     def clean(self): |     def clean(self): | ||||||
|         if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save): |         if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save): | ||||||
|             raise ValidationError( |             raise ValidationError( | ||||||
|                 _("The destination of this transaction must equal to the destination of the template.")) |                 _("The destination of this transaction must equal to the destination of the template.")) | ||||||
|         return super().clean() |         return super().clean() | ||||||
|  |  | ||||||
|     @transaction.atomic |  | ||||||
|     def save(self, *args, **kwargs): |  | ||||||
|         self.clean() |  | ||||||
|         return super().save(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
|         return _('Template') |         return _('Template') | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("recurrent transaction") |  | ||||||
|         verbose_name_plural = _("recurrent transactions") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SpecialTransaction(Transaction): | class SpecialTransaction(Transaction): | ||||||
|     """ |     """ | ||||||
|     Special type of :model:`note.Transaction` associated to transactions with special notes |     Special type of :model:`note.Transaction` associated to transactions with special notes | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     last_name = models.CharField( |     last_name = models.CharField( | ||||||
|         max_length=255, |         max_length=255, | ||||||
|         verbose_name=_("name"), |         verbose_name=_("name"), | ||||||
| @@ -312,6 +310,15 @@ class SpecialTransaction(Transaction): | |||||||
|         blank=True, |         blank=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("Special transaction") | ||||||
|  |         verbose_name_plural = _("Special transactions") | ||||||
|  |  | ||||||
|  |     @transaction.atomic | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         self.clean() | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
|         return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") |         return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") | ||||||
| @@ -325,13 +332,8 @@ class SpecialTransaction(Transaction): | |||||||
|     def clean(self): |     def clean(self): | ||||||
|         # SpecialTransaction are only possible with NoteSpecial object |         # SpecialTransaction are only possible with NoteSpecial object | ||||||
|         if self.is_credit() == self.is_debit(): |         if self.is_credit() == self.is_debit(): | ||||||
|             raise(ValidationError(_("A special transaction is only possible between a" |             raise ValidationError(_("A special transaction is only possible between a" | ||||||
|                                     " Note associated to a payment method and a User or a Club"))) |                                     " Note associated to a payment method and a User or a Club")) | ||||||
|  |  | ||||||
|     @transaction.atomic |  | ||||||
|     def save(self, *args, **kwargs): |  | ||||||
|         self.clean() |  | ||||||
|         super().save(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def validate_payment_form(form): |     def validate_payment_form(form): | ||||||
| @@ -363,17 +365,11 @@ class SpecialTransaction(Transaction): | |||||||
|  |  | ||||||
|         return not error |         return not error | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         verbose_name = _("Special transaction") |  | ||||||
|         verbose_name_plural = _("Special transactions") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MembershipTransaction(Transaction): | class MembershipTransaction(Transaction): | ||||||
|     """ |     """ | ||||||
|     Special type of :model:`note.Transaction` associated to a :model:`member.Membership`. |     Special type of :model:`note.Transaction` associated to a :model:`member.Membership`. | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     membership = models.OneToOneField( |     membership = models.OneToOneField( | ||||||
|         'member.Membership', |         'member.Membership', | ||||||
|         on_delete=models.PROTECT, |         on_delete=models.PROTECT, | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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. | ||||||
| @@ -221,7 +221,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca | |||||||
|     .done(function () { |     .done(function () { | ||||||
|       if (!isNaN(source.balance)) { |       if (!isNaN(source.balance)) { | ||||||
|         const newBalance = source.balance - quantity * amount |         const newBalance = source.balance - quantity * amount | ||||||
|         if (newBalance <= -5000) { |         if (newBalance <= -2000) { | ||||||
|           addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + |           addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + | ||||||
|               'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000) |               'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000) | ||||||
|         } else if (newBalance < 0) { |         } else if (newBalance < 0) { | ||||||
| @@ -258,3 +258,39 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var searchbar = document.getElementById("search-input") | ||||||
|  | var search_results = document.getElementById("search-results") | ||||||
|  |  | ||||||
|  | var old_pattern = null; | ||||||
|  | var firstMatch = null; | ||||||
|  | /** | ||||||
|  |  * Updates the button search tab | ||||||
|  |  * @param force Forces the update even if the pattern didn't change | ||||||
|  |  */ | ||||||
|  | function updateSearch(force = false) { | ||||||
|  |   let pattern = searchbar.value | ||||||
|  |   if (pattern === "") | ||||||
|  |     firstMatch = null; | ||||||
|  |   if ((pattern === old_pattern || pattern === "") && !force) | ||||||
|  |     return; | ||||||
|  |   firstMatch = null; | ||||||
|  |   const re = new RegExp(pattern, "i"); | ||||||
|  |   Array.from(search_results.children).forEach(function(b) { | ||||||
|  |     if (re.test(b.innerText)) { | ||||||
|  |       b.hidden = false; | ||||||
|  |       if (firstMatch === null) { | ||||||
|  |         firstMatch = b; | ||||||
|  |       } | ||||||
|  |     } else | ||||||
|  |       b.hidden = true; | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | searchbar.addEventListener("input", function (e) { | ||||||
|  |   debounce(updateSearch)() | ||||||
|  | }); | ||||||
|  | searchbar.addEventListener("keyup", function (e) { | ||||||
|  |   if (firstMatch && e.key === "Enter") | ||||||
|  |     firstMatch.click() | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -314,7 +314,7 @@ $('#btn_transfer').click(function () { | |||||||
|  |  | ||||||
|           if (!isNaN(source.note.balance)) { |           if (!isNaN(source.note.balance)) { | ||||||
|             const newBalance = source.note.balance - source.quantity * dest.quantity * amount |             const newBalance = source.note.balance - source.quantity * dest.quantity * amount | ||||||
|             if (newBalance <= -5000) { |             if (newBalance <= -2000) { | ||||||
|               addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'), |               addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'), | ||||||
|                   [pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000) |                   [pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000) | ||||||
|               reset() |               reset() | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| import html | import html | ||||||
| @@ -159,11 +159,11 @@ class TrustTable(tables.Table): | |||||||
|         template_name = 'django_tables2/bootstrap4.html' |         template_name = 'django_tables2/bootstrap4.html' | ||||||
|  |  | ||||||
|     show_header = False |     show_header = False | ||||||
|     trusted = tables.Column(attrs={'td': {'class': 'text_center'}}) |     trusted = tables.Column(attrs={'td': {'class': 'text-center'}}) | ||||||
|  |  | ||||||
|     delete_col = tables.TemplateColumn( |     delete_col = tables.TemplateColumn( | ||||||
|         template_code=DELETE_TEMPLATE, |         template_code=DELETE_TEMPLATE, | ||||||
|         extra_context={"delete_trans": _('delete')}, |         extra_context={"delete_trans": _('Delete')}, | ||||||
|         attrs={ |         attrs={ | ||||||
|             'td': { |             'td': { | ||||||
|                 'class': lambda record: 'col-sm-1' |                 'class': lambda record: 'col-sm-1' | ||||||
| @@ -173,6 +173,46 @@ class TrustTable(tables.Table): | |||||||
|         verbose_name=_("Delete"),) |         verbose_name=_("Delete"),) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TrustedTable(tables.Table): | ||||||
|  |     class Meta: | ||||||
|  |         attrs = { | ||||||
|  |             'class': 'table table condensed table-striped', | ||||||
|  |             'id': 'trusted_table' | ||||||
|  |         } | ||||||
|  |         Model = Trust | ||||||
|  |         fields = ("trusting",) | ||||||
|  |         template_name = "django_tables2/bootstrap4.html" | ||||||
|  |  | ||||||
|  |     show_header = False | ||||||
|  |     trusting = tables.Column(attrs={ | ||||||
|  |         'td': {'class': 'text-center', 'width': '100%'}}) | ||||||
|  |  | ||||||
|  |     trust_back = tables.Column( | ||||||
|  |         verbose_name=_("Trust back"), | ||||||
|  |         accessor="pk", | ||||||
|  |         attrs={ | ||||||
|  |             'td': { | ||||||
|  |                 'class': '', | ||||||
|  |                 'id': lambda record: "trust_back_" + str(record.pk), | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     def render_trust_back(self, record): | ||||||
|  |         user_note = record.trusted | ||||||
|  |         trusting_note = record.trusting | ||||||
|  |         if Trust.objects.filter(trusted=trusting_note, trusting=user_note): | ||||||
|  |             return "" | ||||||
|  |         val = '<button id="' | ||||||
|  |         val += str(record.pk) | ||||||
|  |         val += '" class="btn btn-success btn-sm text-nowrap" \ | ||||||
|  |             onclick="create_trust(' + str(record.trusted.pk) + ',' + \ | ||||||
|  |             str(record.trusting.pk) + ')">' | ||||||
|  |         val += str(_("Add back")) | ||||||
|  |         val += '</button>' | ||||||
|  |         return mark_safe(val) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AliasTable(tables.Table): | class AliasTable(tables.Table): | ||||||
|     class Meta: |     class Meta: | ||||||
|         attrs = { |         attrs = { | ||||||
|   | |||||||
| @@ -103,6 +103,11 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|                                 </a> |                                 </a> | ||||||
|                             </li> |                             </li> | ||||||
|                         {% endfor %} |                         {% endfor %} | ||||||
|  |                             <li class="nav-item"> | ||||||
|  |                                 <a class="nav-link font-weight-bold" data-toggle="tab" href="#search"> | ||||||
|  |                                     {% trans "Search" %} | ||||||
|  |                                 </a> | ||||||
|  |                             </li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
| @@ -123,6 +128,20 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         {% endfor %} |                         {% endfor %} | ||||||
|  |                             <div class="tab-pane" id="search"> | ||||||
|  |                                 <input class="form-control mx-auto d-block mb-3" | ||||||
|  |                                        placeholder="{% trans "Search button..." %}" type="search" id="search-input"/> | ||||||
|  |                                 <div class="d-inline-flex flex-wrap justify-content-center" id="search-results"> | ||||||
|  |                                     {% for button in all_buttons %} | ||||||
|  |                                         {% if button.display %} | ||||||
|  |                                             <button class="btn btn-outline-dark rounded-0 flex-fill" hidden | ||||||
|  |                                                     id="search_button{{ button.id }}" name="button" value="{{ button.name }}"> | ||||||
|  |                                                 {{ button.name }} ({{ button.amount | pretty_money }}) | ||||||
|  |                                             </button> | ||||||
|  |                                         {% endif %} | ||||||
|  |                                     {% endfor %} | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
| @@ -163,7 +182,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|     <script type="text/javascript"> |     <script type="text/javascript"> | ||||||
|         {% for button in highlighted %} |         {% for button in highlighted %} | ||||||
|             {% if button.display %} |             {% if button.display %} | ||||||
|                 $("#highlighted_button{{ button.id }}").click(function() { |                 document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() { | ||||||
|                     addConso({{ button.destination_id }}, {{ button.amount }}, |                     addConso({{ button.destination_id }}, {{ button.amount }}, | ||||||
|                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", |                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", | ||||||
|                         {{ button.id }}, "{{ button.name|escapejs }}"); |                         {{ button.id }}, "{{ button.name|escapejs }}"); | ||||||
| @@ -174,7 +193,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|         {% for category in categories %} |         {% for category in categories %} | ||||||
|             {% for button in category.templates_filtered %} |             {% for button in category.templates_filtered %} | ||||||
|             {% if button.display %} |             {% if button.display %} | ||||||
|                 $("#button{{ button.id }}").click(function() { |                 document.getElementById("button{{ button.id }}").addEventListener("click", function() { | ||||||
|                     addConso({{ button.destination_id }}, {{ button.amount }}, |                     addConso({{ button.destination_id }}, {{ button.amount }}, | ||||||
|                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", |                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", | ||||||
|                         {{ button.id }}, "{{ button.name|escapejs }}"); |                         {{ button.id }}, "{{ button.name|escapejs }}"); | ||||||
| @@ -182,5 +201,15 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
|             {% endif %} |             {% endif %} | ||||||
|             {% endfor %} |             {% endfor %} | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|  |  | ||||||
|  |         {% for button in all_buttons %} | ||||||
|  |             {% if button.display %} | ||||||
|  |                 document.getElementById("search_button{{ button.id }}").addEventListener("click", function() { | ||||||
|  |                     addConso({{ button.destination_id }}, {{ button.amount }}, | ||||||
|  |                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", | ||||||
|  |                         {{ button.id }}, "{{ button.name|escapejs }}"); | ||||||
|  |                 }); | ||||||
|  |             {% endif %} | ||||||
|  |         {% endfor %} | ||||||
|     </script> |     </script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| import json | import json | ||||||
| @@ -10,12 +10,12 @@ from django.core.exceptions import PermissionDenied | |||||||
| from django.db.models import Q, F | from django.db.models import Q, F | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from django.views.generic import CreateView, UpdateView, DetailView | from django.views.generic import CreateView, UpdateView, DetailView | ||||||
| from django_tables2 import SingleTableView |  | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
|  | from django_tables2 import SingleTableView | ||||||
| from activity.models import Entry | from activity.models import Entry | ||||||
| from note_kfet.inputs import AmountInput |  | ||||||
| from permission.backends import PermissionBackend | from permission.backends import PermissionBackend | ||||||
| from permission.views import ProtectQuerysetMixin | from permission.views import ProtectQuerysetMixin | ||||||
|  | from note_kfet.inputs import AmountInput | ||||||
|  |  | ||||||
| from .forms import TransactionTemplateForm, SearchTransactionForm | from .forms import TransactionTemplateForm, SearchTransactionForm | ||||||
| from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note | from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note | ||||||
| @@ -190,6 +190,10 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): | |||||||
|         ).order_by('name').all() |         ).order_by('name').all() | ||||||
|         context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk |         context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk | ||||||
|  |  | ||||||
|  |         context['all_buttons'] = TransactionTemplate.objects.filter( | ||||||
|  |             PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view") | ||||||
|  |         ).filter(display=True).order_by('name').all() | ||||||
|  |  | ||||||
|         return context |         return context | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from datetime import date | from datetime import date | ||||||
| @@ -198,6 +198,41 @@ class PermissionBackend(ModelBackend): | |||||||
|     def has_module_perms(self, user_obj, app_label): |     def has_module_perms(self, user_obj, app_label): | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     @memoize | ||||||
|  |     def has_model_perm(request, model, type): | ||||||
|  |         """ | ||||||
|  |         Check is the given user has the permission over a given model for a given action. | ||||||
|  |         The result is then memoized. | ||||||
|  |         :param request: The current request | ||||||
|  |         :param model: The model that the permissions shoud apply | ||||||
|  |         :param type: The type of the permissions: view, change, add or delete | ||||||
|  |         For view action, it is consider possible if user can view or change the model | ||||||
|  |         """ | ||||||
|  |         # Requested by a shell | ||||||
|  |         if request is None: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         user_obj = request.user | ||||||
|  |         sess = request.session | ||||||
|  |  | ||||||
|  |         if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'): | ||||||
|  |             # OAuth2 Authentication | ||||||
|  |             user_obj = request.auth.user | ||||||
|  |  | ||||||
|  |         if user_obj is None or user_obj.is_anonymous: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42: | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |         ct = ContentType.objects.get_for_model(model) | ||||||
|  |         if any(PermissionBackend.permissions(request, ct, type)): | ||||||
|  |             return True | ||||||
|  |         if type == "view" and any(PermissionBackend.permissions(request, ct, "change")): | ||||||
|  |             return True | ||||||
|  |         return False | ||||||
|  |  | ||||||
|     def get_all_permissions(self, user_obj, obj=None): |     def get_all_permissions(self, user_obj, obj=None): | ||||||
|         ct = ContentType.objects.get_for_model(obj) |         ct = ContentType.objects.get_for_model(obj) | ||||||
|         return list(self.permissions(get_current_request(), ct, "view")) |         return list(self.permissions(get_current_request(), ct, "view")) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										19
									
								
								apps/permission/migrations/0002_club_not_required.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								apps/permission/migrations/0002_club_not_required.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # Generated by Django 2.2.28 on 2023-07-24 10:15 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('permission', '0001_initial'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='role', | ||||||
|  |             name='for_club', | ||||||
|  |             field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='for club'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -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): | ||||||
|     """ |     """ | ||||||
| @@ -339,13 +338,14 @@ class Role(models.Model): | |||||||
|         "member.Club", |         "member.Club", | ||||||
|         verbose_name=_("for club"), |         verbose_name=_("for club"), | ||||||
|         on_delete=models.PROTECT, |         on_delete=models.PROTECT, | ||||||
|  |         blank=True, | ||||||
|         null=True, |         null=True, | ||||||
|         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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| default_app_config = 'registration.apps.RegistrationConfig' | default_app_config = 'registration.apps.RegistrationConfig' | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from django import forms | from django import forms | ||||||
| from django.contrib.auth.forms import UserCreationForm | from django.contrib.auth.forms import UserCreationForm | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
|  | # from member.models import Club | ||||||
| from note.models import NoteSpecial, Alias | from note.models import NoteSpecial, Alias | ||||||
| from note_kfet.inputs import AmountInput | from note_kfet.inputs import AmountInput | ||||||
|  |  | ||||||
| @@ -44,14 +45,14 @@ class SignUpForm(UserCreationForm): | |||||||
|         fields = ('first_name', 'last_name', 'username', 'email', ) |         fields = ('first_name', 'last_name', 'username', 'email', ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeclareSogeAccountOpenedForm(forms.Form): | # class DeclareSogeAccountOpenedForm(forms.Form): | ||||||
|     soge_account = forms.BooleanField( | #     soge_account = forms.BooleanField( | ||||||
|         label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE " | #         label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE " | ||||||
|                 "partnership."), | #                 "partnership."), | ||||||
|         help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your " | #         help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your " | ||||||
|                     "account, you will have to pay the BDE membership."), | #                     "account, you will have to pay the BDE membership."), | ||||||
|         required=False, | #         required=False, | ||||||
|     ) | #     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class WEISignupForm(forms.Form): | class WEISignupForm(forms.Form): | ||||||
| @@ -67,11 +68,11 @@ class ValidationForm(forms.Form): | |||||||
|     """ |     """ | ||||||
|     Validate the inscription of the new users and pay memberships. |     Validate the inscription of the new users and pay memberships. | ||||||
|     """ |     """ | ||||||
|     soge = forms.BooleanField( | #    soge = forms.BooleanField( | ||||||
|         label=_("Inscription paid by Société Générale"), | #        label=_("Inscription paid by Société Générale"), | ||||||
|         required=False, | #        required=False, | ||||||
|         help_text=_("Check this case if the Société Générale paid the inscription."), | #        help_text=_("Check this case if the Société Générale paid the inscription."), | ||||||
|     ) | #    ) | ||||||
|  |  | ||||||
|     credit_type = forms.ModelChoiceField( |     credit_type = forms.ModelChoiceField( | ||||||
|         queryset=NoteSpecial.objects, |         queryset=NoteSpecial.objects, | ||||||
| @@ -114,3 +115,12 @@ class ValidationForm(forms.Form): | |||||||
|         required=False, |         required=False, | ||||||
|         initial=True, |         initial=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | #     If the bda exists | ||||||
|  | #     if Club.objects.filter(name__iexact="bda").exists(): | ||||||
|  | #         The user can join the bda club at the inscription | ||||||
|  | #         join_bda = forms.BooleanField( | ||||||
|  | #             label=_("Join BDA Club"), | ||||||
|  | #             required=False, | ||||||
|  | #             initial=True, | ||||||
|  | #         ) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user