mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-30 23:39:54 +01:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			d3e832d23a
			...
			fix_pipeli
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 81c12436a8 | ||
|  | 8eee14075c | ||
|  | ab9ba62af1 | ||
|  | 8ca0648e75 | ||
|  | 77ecfd6ed5 | ||
|  | 0ab1367e55 | ||
|  | 524f0e098a | ||
|  | 1ca4246cbd | 
| @@ -8,19 +8,19 @@ variables: | ||||
|   GIT_SUBMODULE_STRATEGY: recursive | ||||
|  | ||||
| # Debian Buster | ||||
| #  py37-django22: | ||||
| #   stage: test | ||||
| #   image: debian:buster-backports | ||||
| #   before_script: | ||||
| #     - > | ||||
| #         apt-get update && | ||||
| #         apt-get install --no-install-recommends -t buster-backports -y | ||||
| #         python3-django python3-django-crispy-forms | ||||
| #         python3-django-extensions python3-django-filters python3-django-polymorphic | ||||
| #         python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil | ||||
| #         python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache | ||||
| #         python3-bs4 python3-setuptools tox texlive-xetex | ||||
| #   script: tox -e py37-django22 | ||||
| py37-django22: | ||||
|   stage: test | ||||
|   image: debian:buster-backports | ||||
|   before_script: | ||||
|     - > | ||||
|         apt-get update && | ||||
|         apt-get install --no-install-recommends -t buster-backports -y | ||||
|         python3-django python3-django-crispy-forms | ||||
|         python3-django-extensions python3-django-filters python3-django-polymorphic | ||||
|         python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil | ||||
|         python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache | ||||
|         python3-bs4 python3-setuptools tox texlive-xetex | ||||
|   script: tox -e py37-django22 | ||||
|  | ||||
| # Ubuntu 20.04 | ||||
| py38-django22: | ||||
| @@ -56,7 +56,7 @@ py39-django22: | ||||
|  | ||||
| linters: | ||||
|   stage: quality-assurance | ||||
|   image: debian:bullseye | ||||
|   image: debian:buster-backports | ||||
|   before_script: | ||||
|     - apt-get update && apt-get install -y tox | ||||
|   script: tox -e linters | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'activity.apps.ActivityConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework import serializers | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from api.viewsets import ReadProtectedModelViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|             "name": "Pot", | ||||
|             "manage_entries": true, | ||||
|             "can_invite": true, | ||||
|             "guest_entry_fee": 1000 | ||||
|             "guest_entry_fee": 500 | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
| @@ -28,25 +28,5 @@ | ||||
|             "can_invite": false, | ||||
|             "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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta | ||||
|   | ||||
| @@ -1,18 +0,0 @@ | ||||
| # 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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
| @@ -66,8 +66,6 @@ class Activity(models.Model): | ||||
|  | ||||
|     description = models.TextField( | ||||
|         verbose_name=_('description'), | ||||
|         blank=True, | ||||
|         default="", | ||||
|     ) | ||||
|  | ||||
|     location = models.CharField( | ||||
| @@ -125,14 +123,6 @@ class Activity(models.Model): | ||||
|         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 | ||||
|     def save(self, *args, **kwargs): | ||||
|         """ | ||||
| @@ -154,6 +144,14 @@ class Activity(models.Model): | ||||
|                 if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities() | ||||
|         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): | ||||
|     """ | ||||
| @@ -254,13 +252,14 @@ class Guest(models.Model): | ||||
|         verbose_name=_("inviter"), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("guest") | ||||
|         verbose_name_plural = _("guests") | ||||
|         unique_together = ("activity", "last_name", "first_name", ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.first_name + " " + self.last_name | ||||
|     @property | ||||
|     def has_entry(self): | ||||
|         try: | ||||
|             if self.entry: | ||||
|                 return True | ||||
|             return False | ||||
|         except AttributeError: | ||||
|             return False | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def save(self, force_insert=False, force_update=False, using=None, update_fields=None): | ||||
| @@ -291,14 +290,13 @@ class Guest(models.Model): | ||||
|  | ||||
|         return super().save(force_insert, force_update, using, update_fields) | ||||
|  | ||||
|     @property | ||||
|     def has_entry(self): | ||||
|         try: | ||||
|             if self.entry: | ||||
|                 return True | ||||
|             return False | ||||
|         except AttributeError: | ||||
|             return False | ||||
|     def __str__(self): | ||||
|         return self.first_name + " " + self.last_name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("guest") | ||||
|         verbose_name_plural = _("guests") | ||||
|         unique_together = ("activity", "last_name", "first_name", ) | ||||
|  | ||||
|  | ||||
| class GuestTransaction(Transaction): | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.utils import timezone | ||||
|   | ||||
| @@ -18,26 +18,3 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   </div> | ||||
| </div> | ||||
| {% 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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from hashlib import md5 | ||||
| @@ -76,7 +76,6 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView | ||||
|         context['upcoming'] = ActivityTable( | ||||
|             data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), | ||||
|             prefix='upcoming-', | ||||
|             order_by='date_start', | ||||
|         ) | ||||
|  | ||||
|         started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'api.apps.APIConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| from rest_framework.pagination import PageNumberPagination | ||||
|  | ||||
|  | ||||
| class CustomPagination(PageNumberPagination): | ||||
|     page_size_query_param = 'page_size' | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import json | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'logs.apps.LogsConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework import serializers | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .views import ChangelogViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
| @@ -76,6 +76,9 @@ class Changelog(models.Model): | ||||
|         verbose_name=_('timestamp'), | ||||
|     ) | ||||
|  | ||||
|     def delete(self, using=None, keep_parents=False): | ||||
|         raise ValidationError(_("Logs cannot be destroyed.")) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("changelog") | ||||
|         verbose_name_plural = _("changelogs") | ||||
| @@ -83,6 +86,3 @@ class Changelog(models.Model): | ||||
|     def __str__(self): | ||||
|         return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format( | ||||
|             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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'member.apps.MemberConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework import serializers | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .views import ProfileViewSet, ClubViewSet, MembershipViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from cas_server.auth import DjangoAuthUser  # pragma: no cover | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import io | ||||
| @@ -47,13 +47,6 @@ class ProfileForm(forms.ModelForm): | ||||
|  | ||||
|     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): | ||||
|         promotion = self.cleaned_data["promotion"] | ||||
|         if promotion > timezone.now().year: | ||||
| @@ -121,7 +114,7 @@ class ImageForm(forms.Form): | ||||
|                 frame = frame.crop((x, y, x + w, y + h)) | ||||
|                 frame = frame.resize( | ||||
|                     (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH), | ||||
|                     Image.LANCZOS, | ||||
|                     Image.ANTIALIAS, | ||||
|                 ) | ||||
|                 frames.append(frame) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import hashlib | ||||
|   | ||||
| @@ -1,18 +0,0 @@ | ||||
| # 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'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -1,18 +0,0 @@ | ||||
| # 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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import datetime | ||||
| @@ -28,6 +28,7 @@ class Profile(models.Model): | ||||
|     We do not want to patch the Django Contrib :model:`auth.User`model; | ||||
|     so this model add an user profile with additional information. | ||||
|     """ | ||||
|  | ||||
|     user = models.OneToOneField( | ||||
|         settings.AUTH_USER_MODEL, | ||||
|         on_delete=models.CASCADE, | ||||
| @@ -133,22 +134,6 @@ class Profile(models.Model): | ||||
|         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 | ||||
|     def ens_year(self): | ||||
|         """ | ||||
| @@ -173,6 +158,17 @@ class Profile(models.Model): | ||||
|             return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() | ||||
|         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): | ||||
|         subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) | ||||
|         token = email_validation_token.make_token(self.user) | ||||
| @@ -204,11 +200,9 @@ class Club(models.Model): | ||||
|         max_length=255, | ||||
|         unique=True, | ||||
|     ) | ||||
|  | ||||
|     email = models.EmailField( | ||||
|         verbose_name=_('email'), | ||||
|     ) | ||||
|  | ||||
|     parent_club = models.ForeignKey( | ||||
|         'self', | ||||
|         null=True, | ||||
| @@ -259,12 +253,25 @@ class Club(models.Model): | ||||
|         help_text=_('Maximal date of a membership, after which members must renew it.'), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("club") | ||||
|         verbose_name_plural = _("clubs") | ||||
|     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 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|         today = datetime.date.today() | ||||
|  | ||||
|         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 | ||||
|     def save(self, force_insert=False, force_update=False, using=None, | ||||
| @@ -277,29 +284,16 @@ class Club(models.Model): | ||||
|             self.membership_end = None | ||||
|         super().save(force_insert, force_update, update_fields) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("club") | ||||
|         verbose_name_plural = _("clubs") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     def get_absolute_url(self): | ||||
|         return reverse_lazy('member:club_detail', args=(self.pk,)) | ||||
|  | ||||
|     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): | ||||
|     """ | ||||
| @@ -339,66 +333,6 @@ class Membership(models.Model): | ||||
|         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 | ||||
|     def valid(self): | ||||
|         """ | ||||
| @@ -476,6 +410,58 @@ class Membership(models.Model): | ||||
|                 parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) | ||||
|             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): | ||||
|         """ | ||||
|         Create Membership transaction associated to this membership. | ||||
| @@ -513,3 +499,11 @@ class Membership(models.Model): | ||||
|                 soge_credit.save() | ||||
|             else: | ||||
|                 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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| /** | ||||
|  * On form submit, create a new friendship | ||||
|  */ | ||||
| function form_create_trust (e) { | ||||
| function create_trust (e) { | ||||
|   // Do not submit HTML form | ||||
|   e.preventDefault() | ||||
|  | ||||
| @@ -14,35 +14,25 @@ function form_create_trust (e) { | ||||
|          addMsg(gettext("You can't add yourself as a friend"), "danger") | ||||
|          return | ||||
|       } | ||||
|       create_trust(formData.get('trusting'), trusted_alias.note) | ||||
|       $.post('/api/note/trust/', { | ||||
|         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) { | ||||
|         errMsg(xhr.responseJSON) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Create a trust between users | ||||
|  * @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 | ||||
|  * On click of "delete", delete the alias | ||||
|  * @param button_id:Integer Alias id to remove | ||||
|  */ | ||||
| function delete_button (button_id) { | ||||
|   $.ajax({ | ||||
| @@ -52,7 +42,6 @@ function delete_button (button_id) { | ||||
|   }).done(function () { | ||||
|     addMsg(gettext('Friendship successfully deleted'), 'success') | ||||
|     $('#trust_table').load(location.pathname + ' #trust_table') | ||||
|     $('#trusted_table').load(location.pathname + ' #trusted_table') | ||||
|   }).fail(function (xhr, _textStatus, _error) { | ||||
|     errMsg(xhr.responseJSON) | ||||
|   }) | ||||
| @@ -60,5 +49,5 @@ function delete_button (button_id) { | ||||
|  | ||||
| $(document).ready(function () { | ||||
|   // Attach event | ||||
|   document.getElementById('form_trust').addEventListener('submit', form_create_trust) | ||||
|   document.getElementById('form_trust').addEventListener('submit', create_trust) | ||||
| }) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import date | ||||
|   | ||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
| {% block profile_content %} | ||||
| <div class="card bg-light mb-3"> | ||||
|     <h3 class="card-header text-center"> | ||||
|         {% trans "Add friends" %} | ||||
|         {% trans "Note friendships" %} | ||||
|     </h3> | ||||
|     <div class="card-body"> | ||||
|         {% if can_create %} | ||||
| @@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|     {% render_table trusting %} | ||||
| </div> | ||||
|  | ||||
| <div class="alert alert-warning card mb-3"> | ||||
| <div class="alert alert-warning card"> | ||||
|     {% blocktrans trimmed %} | ||||
|         Adding someone as a friend enables them to initiate transactions coming | ||||
|         from your account (while keeping your balance positive). This is | ||||
| @@ -33,13 +33,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|         friends without needing additional rights among them. | ||||
|     {% endblocktrans %} | ||||
| </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 %} | ||||
|  | ||||
| {% block extrajavascript %} | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import date | ||||
|   | ||||
| @@ -183,7 +183,7 @@ class TestMemberships(TestCase): | ||||
|                 club = Club.objects.get(name="Kfet") | ||||
|             else: | ||||
|                 club = Club.objects.create( | ||||
|                     name="Second club without BDE", | ||||
|                     name="Second club " + ("with BDE" if bde_parent else "without BDE"), | ||||
|                     parent_club=None, | ||||
|                     email="newclub@example.com", | ||||
|                     require_memberships=True, | ||||
| @@ -335,7 +335,6 @@ class TestMemberships(TestCase): | ||||
|             ml_sports_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             report_frequency=7, | ||||
|             VSS_charter_read=True | ||||
|         )) | ||||
|         self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) | ||||
|         self.assertTrue(User.objects.filter(username="toto changed").exists()) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta, date | ||||
| @@ -8,6 +8,7 @@ from django.contrib.auth import logout | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth.views import LoginView | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.db import transaction | ||||
| from django.db.models import Q, F | ||||
| from django.shortcuts import redirect | ||||
| @@ -20,13 +21,13 @@ from django_tables2.views import SingleTableView | ||||
| from rest_framework.authtoken.models import Token | ||||
| from note.models import Alias, NoteClub, NoteUser, Trust | ||||
| from note.models.transactions import Transaction, SpecialTransaction | ||||
| from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable | ||||
| from note.tables import HistoryTable, AliasTable, TrustTable | ||||
| from note_kfet.middlewares import _set_current_request | ||||
| from permission.backends import PermissionBackend | ||||
| from permission.models import Role | ||||
| from permission.views import ProtectQuerysetMixin, ProtectedCreateView | ||||
|  | ||||
| from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ | ||||
| from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ | ||||
|     CustomAuthenticationForm, MembershipRolesForm | ||||
| from .models import Club, Membership | ||||
| from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable | ||||
| @@ -257,18 +258,17 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): | ||||
|         note = context['object'].note | ||||
|         context["trusting"] = TrustTable( | ||||
|             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( | ||||
|             trusting=context["object"].note, | ||||
|             trusted=context["object"].note | ||||
|         )) | ||||
|         context["widget"] = { | ||||
|             "name": "trusted", | ||||
|             "resetable": True, | ||||
|             "attrs": { | ||||
|                 "model_pk": ContentType.objects.get_for_model(Alias).pk, | ||||
|                 "class": "autocomplete form-control", | ||||
|                 "id": "trusted", | ||||
|                 "resetable": True, | ||||
|                 "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", | ||||
|                 "name_field": "name", | ||||
|                 "placeholder": "" | ||||
| @@ -753,10 +753,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): | ||||
|             club = old_membership.club | ||||
|             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 | ||||
|  | ||||
|         # Get form data | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'note.apps.NoteConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
| @@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \ | ||||
|     PolymorphicChildModelFilter, PolymorphicParentModelAdmin | ||||
| from note_kfet.admin import admin_site | ||||
|  | ||||
| from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust | ||||
| from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser | ||||
| from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ | ||||
|     RecurrentTransaction, MembershipTransaction, SpecialTransaction | ||||
| from .templatetags.pretty_money import pretty_money | ||||
| @@ -21,16 +21,6 @@ class AliasInlines(admin.TabularInline): | ||||
|     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) | ||||
| class NoteAdmin(PolymorphicParentModelAdmin): | ||||
|     """ | ||||
| @@ -102,7 +92,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin): | ||||
|     """ | ||||
|     Child for an user note, see NoteAdmin | ||||
|     """ | ||||
|     inlines = (AliasInlines, TrustInlines) | ||||
|     inlines = (AliasInlines,) | ||||
|  | ||||
|     # We can't change user after creation or the balance | ||||
|     readonly_fields = ('user', 'balance') | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
| @@ -11,7 +11,6 @@ from member.models import Membership | ||||
| from note_kfet.middlewares import get_current_request | ||||
| from permission.backends import PermissionBackend | ||||
| 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.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ | ||||
| @@ -87,9 +86,11 @@ class TrustSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Trust | ||||
|         fields = '__all__' | ||||
|         validators = [UniqueTogetherValidator( | ||||
|             queryset=Trust.objects.all(), fields=('trusting', 'trusted'), | ||||
|             message=_("This friendship already exists"))] | ||||
|  | ||||
|     def validate(self, attrs): | ||||
|         instance = Trust(**attrs) | ||||
|         instance.clean() | ||||
|         return attrs | ||||
|  | ||||
|  | ||||
| class AliasSerializer(serializers.ModelSerializer): | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \ | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| import re | ||||
|  | ||||
| @@ -13,7 +13,7 @@ from rest_framework import status | ||||
| from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet | ||||
| from permission.backends import PermissionBackend | ||||
|  | ||||
| from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \ | ||||
| from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ | ||||
|     TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ | ||||
|     TrustSerializer | ||||
| from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| from datetime import datetime | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import unicodedata | ||||
| @@ -293,11 +293,6 @@ class Alias(models.Model): | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def save(self, *args, **kwargs): | ||||
|         self.clean() | ||||
|         super().save(*args, **kwargs) | ||||
|  | ||||
|     @staticmethod | ||||
|     def normalize(string): | ||||
|         """ | ||||
| @@ -326,6 +321,11 @@ class Alias(models.Model): | ||||
|             pass | ||||
|         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): | ||||
|         if self.name == str(self.note): | ||||
|             raise ValidationError(_("You can't delete your main alias."), | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.core.exceptions import ValidationError | ||||
| @@ -59,7 +59,6 @@ class TransactionTemplate(models.Model): | ||||
|     amount = models.PositiveIntegerField( | ||||
|         verbose_name=_('amount'), | ||||
|     ) | ||||
|  | ||||
|     category = models.ForeignKey( | ||||
|         TemplateCategory, | ||||
|         on_delete=models.PROTECT, | ||||
| @@ -88,12 +87,12 @@ class TransactionTemplate(models.Model): | ||||
|         verbose_name = _("transaction template") | ||||
|         verbose_name_plural = _("transaction templates") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     def get_absolute_url(self): | ||||
|         return reverse('note:template_update', args=(self.pk,)) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|  | ||||
| class Transaction(PolymorphicModel): | ||||
|     """ | ||||
| @@ -102,6 +101,7 @@ class Transaction(PolymorphicModel): | ||||
|     amount is store in centimes of currency, making it a  positive integer | ||||
|     value. (from someone to someone else) | ||||
|     """ | ||||
|  | ||||
|     source = models.ForeignKey( | ||||
|         Note, | ||||
|         on_delete=models.PROTECT, | ||||
| @@ -166,50 +166,6 @@ class Transaction(PolymorphicModel): | ||||
|             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): | ||||
|         previous_source_balance = self.source.balance | ||||
|         previous_dest_balance = self.destination.balance | ||||
| @@ -252,6 +208,46 @@ class Transaction(PolymorphicModel): | ||||
|  | ||||
|         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 | ||||
|     def total(self): | ||||
|         return self.amount * self.quantity | ||||
| @@ -260,40 +256,46 @@ class Transaction(PolymorphicModel): | ||||
|     def type(self): | ||||
|         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): | ||||
|     """ | ||||
|     Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`. | ||||
|     """ | ||||
|  | ||||
|     template = models.ForeignKey( | ||||
|         TransactionTemplate, | ||||
|         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): | ||||
|         if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save): | ||||
|             raise ValidationError( | ||||
|                 _("The destination of this transaction must equal to the destination of the template.")) | ||||
|         return super().clean() | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def save(self, *args, **kwargs): | ||||
|         self.clean() | ||||
|         return super().save(*args, **kwargs) | ||||
|  | ||||
|     @property | ||||
|     def type(self): | ||||
|         return _('Template') | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("recurrent transaction") | ||||
|         verbose_name_plural = _("recurrent transactions") | ||||
|  | ||||
|  | ||||
| class SpecialTransaction(Transaction): | ||||
|     """ | ||||
|     Special type of :model:`note.Transaction` associated to transactions with special notes | ||||
|     """ | ||||
|  | ||||
|     last_name = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_("name"), | ||||
| @@ -310,15 +312,6 @@ class SpecialTransaction(Transaction): | ||||
|         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 | ||||
|     def type(self): | ||||
|         return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") | ||||
| @@ -332,8 +325,13 @@ class SpecialTransaction(Transaction): | ||||
|     def clean(self): | ||||
|         # SpecialTransaction are only possible with NoteSpecial object | ||||
|         if self.is_credit() == self.is_debit(): | ||||
|             raise ValidationError(_("A special transaction is only possible between a" | ||||
|                                     " Note associated to a payment method and a User or a Club")) | ||||
|             raise(ValidationError(_("A special transaction is only possible between a" | ||||
|                                     " 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 | ||||
|     def validate_payment_form(form): | ||||
| @@ -365,11 +363,17 @@ class SpecialTransaction(Transaction): | ||||
|  | ||||
|         return not error | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("Special transaction") | ||||
|         verbose_name_plural = _("Special transactions") | ||||
|  | ||||
|  | ||||
| class MembershipTransaction(Transaction): | ||||
|     """ | ||||
|     Special type of :model:`note.Transaction` associated to a :model:`member.Membership`. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     membership = models.OneToOneField( | ||||
|         'member.Membership', | ||||
|         on_delete=models.PROTECT, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.utils import timezone | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| // Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| // Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| // 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 () { | ||||
|       if (!isNaN(source.balance)) { | ||||
|         const newBalance = source.balance - quantity * amount | ||||
|         if (newBalance <= -2000) { | ||||
|         if (newBalance <= -5000) { | ||||
|           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) | ||||
|         } else if (newBalance < 0) { | ||||
| @@ -258,39 +258,3 @@ 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)) { | ||||
|             const newBalance = source.note.balance - source.quantity * dest.quantity * amount | ||||
|             if (newBalance <= -2000) { | ||||
|             if (newBalance <= -5000) { | ||||
|               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) | ||||
|               reset() | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import html | ||||
| @@ -159,11 +159,11 @@ class TrustTable(tables.Table): | ||||
|         template_name = 'django_tables2/bootstrap4.html' | ||||
|  | ||||
|     show_header = False | ||||
|     trusted = tables.Column(attrs={'td': {'class': 'text-center'}}) | ||||
|     trusted = tables.Column(attrs={'td': {'class': 'text_center'}}) | ||||
|  | ||||
|     delete_col = tables.TemplateColumn( | ||||
|         template_code=DELETE_TEMPLATE, | ||||
|         extra_context={"delete_trans": _('Delete')}, | ||||
|         extra_context={"delete_trans": _('delete')}, | ||||
|         attrs={ | ||||
|             'td': { | ||||
|                 'class': lambda record: 'col-sm-1' | ||||
| @@ -173,46 +173,6 @@ class TrustTable(tables.Table): | ||||
|         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 Meta: | ||||
|         attrs = { | ||||
|   | ||||
| @@ -103,11 +103,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|                                 </a> | ||||
|                             </li> | ||||
|                         {% endfor %} | ||||
|                             <li class="nav-item"> | ||||
|                                 <a class="nav-link font-weight-bold" data-toggle="tab" href="#search"> | ||||
|                                     {% trans "Search" %} | ||||
|                                 </a> | ||||
|                             </li> | ||||
|                     </ul> | ||||
|                 </div> | ||||
|  | ||||
| @@ -128,20 +123,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         {% 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> | ||||
|  | ||||
| @@ -182,7 +163,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|     <script type="text/javascript"> | ||||
|         {% for button in highlighted %} | ||||
|             {% if button.display %} | ||||
|                 document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() { | ||||
|                 $("#highlighted_button{{ button.id }}").click(function() { | ||||
|                     addConso({{ button.destination_id }}, {{ button.amount }}, | ||||
|                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", | ||||
|                         {{ button.id }}, "{{ button.name|escapejs }}"); | ||||
| @@ -193,7 +174,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|         {% for category in categories %} | ||||
|             {% for button in category.templates_filtered %} | ||||
|             {% if button.display %} | ||||
|                 document.getElementById("button{{ button.id }}").addEventListener("click", function() { | ||||
|                 $("#button{{ button.id }}").click(function() { | ||||
|                     addConso({{ button.destination_id }}, {{ button.amount }}, | ||||
|                         {{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}", | ||||
|                         {{ button.id }}, "{{ button.name|escapejs }}"); | ||||
| @@ -201,15 +182,5 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|             {% endif %} | ||||
|             {% 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> | ||||
| {% 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 }}. | ||||
|  | ||||
| Par ailleurs, le BDE ne sert pas d'alcool aux adhérent·e·s dont le solde | ||||
| Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde | ||||
| est inférieur à 0 € depuis plus de 24h. | ||||
|  | ||||
| Si tu ne comprends pas ton solde, tu peux consulter ton historique | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import template | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import template | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from api.tests import TestAPI | ||||
| @@ -10,7 +10,7 @@ from django.urls import reverse | ||||
| from django.utils import timezone | ||||
| from permission.models import Role | ||||
|  | ||||
| from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \ | ||||
| from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\ | ||||
|     TransactionTemplateViewSet, TransactionViewSet | ||||
| from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \ | ||||
|     MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import json | ||||
| @@ -10,12 +10,12 @@ from django.core.exceptions import PermissionDenied | ||||
| from django.db.models import Q, F | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import CreateView, UpdateView, DetailView | ||||
| from django.urls import reverse_lazy | ||||
| from django_tables2 import SingleTableView | ||||
| from django.urls import reverse_lazy | ||||
| from activity.models import Entry | ||||
| from note_kfet.inputs import AmountInput | ||||
| from permission.backends import PermissionBackend | ||||
| from permission.views import ProtectQuerysetMixin | ||||
| from note_kfet.inputs import AmountInput | ||||
|  | ||||
| from .forms import TransactionTemplateForm, SearchTransactionForm | ||||
| from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note | ||||
| @@ -190,10 +190,6 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): | ||||
|         ).order_by('name').all() | ||||
|         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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'permission.apps.PermissionConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-lateré | ||||
|  | ||||
| from django.contrib import admin | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework import serializers | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from .views import PermissionViewSet, RoleViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from api.viewsets import ReadOnlyProtectedModelViewSet | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import date | ||||
| @@ -198,41 +198,6 @@ class PermissionBackend(ModelBackend): | ||||
|     def has_module_perms(self, user_obj, app_label): | ||||
|         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): | ||||
|         ct = ContentType.objects.get_for_model(obj) | ||||
|         return list(self.permissions(get_current_request(), ct, "view")) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| import sys | ||||
| from functools import lru_cache | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,19 +0,0 @@ | ||||
| # 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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import functools | ||||
| @@ -26,15 +26,6 @@ class InstancedPermission: | ||||
|         self.mask = mask | ||||
|         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): | ||||
|         """ | ||||
|         Returns True if the permission applies to | ||||
| @@ -93,11 +84,21 @@ class InstancedPermission: | ||||
|             # noinspection PyProtectedMember | ||||
|             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): | ||||
|     """ | ||||
|     Permissions that are hidden behind a mask | ||||
|     """ | ||||
|  | ||||
|     rank = models.PositiveSmallIntegerField( | ||||
|         unique=True, | ||||
|         verbose_name=_('rank'), | ||||
| @@ -109,13 +110,13 @@ class PermissionMask(models.Model): | ||||
|         verbose_name=_('description'), | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.description | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("permission mask") | ||||
|         verbose_name_plural = _("permission masks") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.description | ||||
|  | ||||
|  | ||||
| class Permission(models.Model): | ||||
|  | ||||
| @@ -193,19 +194,16 @@ class Permission(models.Model): | ||||
|         verbose_name = _("permission") | ||||
|         verbose_name_plural = _("permissions") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.description | ||||
|     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.")) | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def save(self, **kwargs): | ||||
|         self.full_clean() | ||||
|         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 | ||||
|     def compute_f(oper, **kwargs): | ||||
|         if isinstance(oper, list): | ||||
| @@ -319,6 +317,9 @@ class Permission(models.Model): | ||||
|         # query = self._about(query, **kwargs) | ||||
|         return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.description | ||||
|  | ||||
|  | ||||
| class Role(models.Model): | ||||
|     """ | ||||
| @@ -338,14 +339,13 @@ class Role(models.Model): | ||||
|         "member.Club", | ||||
|         verbose_name=_("for club"), | ||||
|         on_delete=models.PROTECT, | ||||
|         blank=True, | ||||
|         null=True, | ||||
|         default=None, | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("role permissions") | ||||
|         verbose_name_plural = _("role permissions") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework.permissions import DjangoObjectPermissions | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| from oauth2_provider.oauth2_validators import OAuth2Validator | ||||
| from oauth2_provider.scopes import BaseScopes | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.core.exceptions import PermissionDenied | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import django_tables2 as tables | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta, date | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import date | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| from collections import OrderedDict | ||||
| from datetime import date | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'registration.apps.RegistrationConfig' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import forms | ||||
| from django.contrib.auth.forms import UserCreationForm | ||||
| from django.contrib.auth.models import User | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| # from member.models import Club | ||||
| from note.models import NoteSpecial, Alias | ||||
| from note_kfet.inputs import AmountInput | ||||
|  | ||||
| @@ -45,14 +44,14 @@ class SignUpForm(UserCreationForm): | ||||
|         fields = ('first_name', 'last_name', 'username', 'email', ) | ||||
|  | ||||
|  | ||||
| # class DeclareSogeAccountOpenedForm(forms.Form): | ||||
| #     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 " | ||||
| #                 "partnership."), | ||||
| #         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."), | ||||
| #         required=False, | ||||
| #     ) | ||||
| class DeclareSogeAccountOpenedForm(forms.Form): | ||||
|     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 " | ||||
|                 "partnership."), | ||||
|         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."), | ||||
|         required=False, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class WEISignupForm(forms.Form): | ||||
| @@ -68,11 +67,11 @@ class ValidationForm(forms.Form): | ||||
|     """ | ||||
|     Validate the inscription of the new users and pay memberships. | ||||
|     """ | ||||
| #    soge = forms.BooleanField( | ||||
| #        label=_("Inscription paid by Société Générale"), | ||||
| #        required=False, | ||||
| #        help_text=_("Check this case if the Société Générale paid the inscription."), | ||||
| #    ) | ||||
|     soge = forms.BooleanField( | ||||
|         label=_("Inscription paid by Société Générale"), | ||||
|         required=False, | ||||
|         help_text=_("Check this case if the Société Générale paid the inscription."), | ||||
|     ) | ||||
|  | ||||
|     credit_type = forms.ModelChoiceField( | ||||
|         queryset=NoteSpecial.objects, | ||||
| @@ -115,12 +114,3 @@ class ValidationForm(forms.Form): | ||||
|         required=False, | ||||
|         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-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import django_tables2 as tables | ||||
|   | ||||
| @@ -57,13 +57,11 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|                         <h4> {% trans "Validate account" %}</h4> | ||||
|                     </div> | ||||
|  | ||||
|                     {% comment "Soge not for membership (only WEI)" %} | ||||
|                     {% if declare_soge_account %} | ||||
|                         <div class="alert alert-info"> | ||||
|                         {% trans "The user declared that he/she opened a bank account in the Société générale." %} | ||||
|                         </div> | ||||
|                     {% endif %} | ||||
|                     {% endcomment %} | ||||
|  | ||||
|                     <div class="card-body" id="profile_infos"> | ||||
|                         {% csrf_token %} | ||||
| @@ -78,7 +76,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|     </div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% comment "Soge not for membership (only WEI)" %} | ||||
| {% block extrajavascript %} | ||||
|     <script> | ||||
|         soge_field = $("#id_soge"); | ||||
| @@ -121,4 +118,3 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|         {% endif %} | ||||
|     </script> | ||||
| {% endblock %} | ||||
| {% endcomment %} | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
| @@ -48,7 +48,6 @@ class TestSignup(TestCase): | ||||
|             ml_events_registration="en", | ||||
|             ml_sport_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             VSS_charter_read=True | ||||
|         )) | ||||
|         self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200) | ||||
|         self.assertTrue(User.objects.filter(username="toto").exists()) | ||||
| @@ -106,7 +105,6 @@ class TestSignup(TestCase): | ||||
|             ml_events_registration="en", | ||||
|             ml_sport_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             VSS_charter_read=True | ||||
|         )) | ||||
|         self.assertTrue(response.status_code, 200) | ||||
|  | ||||
| @@ -126,7 +124,6 @@ class TestSignup(TestCase): | ||||
|             ml_events_registration="en", | ||||
|             ml_sport_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             VSS_charter_read=True | ||||
|         )) | ||||
|         self.assertTrue(response.status_code, 200) | ||||
|  | ||||
| @@ -146,27 +143,6 @@ class TestSignup(TestCase): | ||||
|             ml_events_registration="en", | ||||
|             ml_sport_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             VSS_charter_read=True | ||||
|         )) | ||||
|         self.assertTrue(response.status_code, 200) | ||||
|  | ||||
|         # The VSS charter is not read | ||||
|         response = self.client.post(reverse("registration:signup"), dict( | ||||
|             first_name="Toto", | ||||
|             last_name="TOTO", | ||||
|             username="Ihaveanotherusername", | ||||
|             email="othertoto@example.com", | ||||
|             password1="toto1234", | ||||
|             password2="toto1234", | ||||
|             phone_number="+33123456789", | ||||
|             department="EXT", | ||||
|             promotion=Club.objects.get(name="BDE").membership_start.year, | ||||
|             address="Earth", | ||||
|             paid=False, | ||||
|             ml_events_registration="en", | ||||
|             ml_sport_registration=True, | ||||
|             ml_art_registration=True, | ||||
|             VSS_charter_read=False | ||||
|         )) | ||||
|         self.assertTrue(response.status_code, 200) | ||||
|  | ||||
| @@ -214,7 +190,7 @@ class TestValidateRegistration(TestCase): | ||||
|  | ||||
|         # BDE Membership is mandatory | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Chèque").id, | ||||
|             credit_amount=4200, | ||||
|             last_name="TOTO", | ||||
| @@ -228,7 +204,7 @@ class TestValidateRegistration(TestCase): | ||||
|  | ||||
|         # Same | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type="", | ||||
|             credit_amount=0, | ||||
|             last_name="TOTO", | ||||
| @@ -242,7 +218,7 @@ class TestValidateRegistration(TestCase): | ||||
|  | ||||
|         # The BDE membership is not free | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Espèces").id, | ||||
|             credit_amount=0, | ||||
|             last_name="TOTO", | ||||
| @@ -256,7 +232,7 @@ class TestValidateRegistration(TestCase): | ||||
|  | ||||
|         # Last and first names are required for a credit | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Chèque").id, | ||||
|             credit_amount=4000, | ||||
|             last_name="", | ||||
| @@ -273,7 +249,7 @@ class TestValidateRegistration(TestCase): | ||||
|         self.user.username = "admïntoto" | ||||
|         self.user.save() | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Chèque").id, | ||||
|             credit_amount=500, | ||||
|             last_name="TOTO", | ||||
| @@ -299,7 +275,7 @@ class TestValidateRegistration(TestCase): | ||||
|         self.user.profile.save() | ||||
|  | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Chèque").id, | ||||
|             credit_amount=500, | ||||
|             last_name="TOTO", | ||||
| @@ -314,7 +290,6 @@ class TestValidateRegistration(TestCase): | ||||
|         self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) | ||||
|         self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) | ||||
|         self.assertFalse(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) | ||||
|         self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists()) | ||||
|         self.assertFalse(SogeCredit.objects.filter(user=self.user).exists()) | ||||
|         self.assertEqual(Transaction.objects.filter( | ||||
|             Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2) | ||||
| @@ -336,7 +311,7 @@ class TestValidateRegistration(TestCase): | ||||
|         self.user.profile.save() | ||||
|  | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             # soge=False, | ||||
|             soge=False, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Espèces").id, | ||||
|             credit_amount=4000, | ||||
|             last_name="TOTO", | ||||
| @@ -351,7 +326,6 @@ class TestValidateRegistration(TestCase): | ||||
|         self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) | ||||
|         self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) | ||||
|         self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) | ||||
|         self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists()) | ||||
|         self.assertFalse(SogeCredit.objects.filter(user=self.user).exists()) | ||||
|         self.assertEqual(Transaction.objects.filter( | ||||
|             Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) | ||||
| @@ -359,43 +333,42 @@ class TestValidateRegistration(TestCase): | ||||
|         response = self.client.get(self.user.profile.get_absolute_url()) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
| #    def test_validate_kfet_registration_with_soge(self): | ||||
| #        """ | ||||
| #        The user joins the BDE and the Kfet, but the membership is paid by the Société générale. | ||||
| #        """ | ||||
| #        response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,))) | ||||
| #        self.assertEqual(response.status_code, 200) | ||||
| # | ||||
| #        response = self.client.get(self.user.profile.get_absolute_url()) | ||||
| #        self.assertEqual(response.status_code, 404) | ||||
| # | ||||
| #        self.user.profile.email_confirmed = True | ||||
| #        self.user.profile.save() | ||||
| # | ||||
| #        response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
| #            soge=True, | ||||
| #            credit_type=NoteSpecial.objects.get(special_type="Espèces").id, | ||||
| #            credit_amount=4000, | ||||
| #            last_name="TOTO", | ||||
| #            first_name="Toto", | ||||
| #            bank="Société générale", | ||||
| #            join_bde=True, | ||||
| #            join_kfet=True, | ||||
| #        )) | ||||
| #        self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) | ||||
| #        self.user.profile.refresh_from_db() | ||||
| #        self.assertTrue(self.user.profile.registration_valid) | ||||
| #        self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) | ||||
| #        self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) | ||||
| #        self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) | ||||
| #        self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists()) | ||||
| #        self.assertTrue(SogeCredit.objects.filter(user=self.user).exists()) | ||||
| #        self.assertEqual(Transaction.objects.filter( | ||||
| #            Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) | ||||
| #        self.assertFalse(Transaction.objects.filter(valid=True).exists()) | ||||
| # | ||||
| #        response = self.client.get(self.user.profile.get_absolute_url()) | ||||
| #        self.assertEqual(response.status_code, 200) | ||||
|     def test_validate_kfet_registration_with_soge(self): | ||||
|         """ | ||||
|         The user joins the BDE and the Kfet, but the membership is paid by the Société générale. | ||||
|         """ | ||||
|         response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,))) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         response = self.client.get(self.user.profile.get_absolute_url()) | ||||
|         self.assertEqual(response.status_code, 404) | ||||
|  | ||||
|         self.user.profile.email_confirmed = True | ||||
|         self.user.profile.save() | ||||
|  | ||||
|         response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( | ||||
|             soge=True, | ||||
|             credit_type=NoteSpecial.objects.get(special_type="Espèces").id, | ||||
|             credit_amount=4000, | ||||
|             last_name="TOTO", | ||||
|             first_name="Toto", | ||||
|             bank="Société générale", | ||||
|             join_bde=True, | ||||
|             join_kfet=True, | ||||
|         )) | ||||
|         self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) | ||||
|         self.user.profile.refresh_from_db() | ||||
|         self.assertTrue(self.user.profile.registration_valid) | ||||
|         self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) | ||||
|         self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) | ||||
|         self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) | ||||
|         self.assertTrue(SogeCredit.objects.filter(user=self.user).exists()) | ||||
|         self.assertEqual(Transaction.objects.filter( | ||||
|             Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) | ||||
|         self.assertFalse(Transaction.objects.filter(valid=True).exists()) | ||||
|  | ||||
|         response = self.client.get(self.user.profile.get_absolute_url()) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|     def test_invalidate_registration(self): | ||||
|         """ | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user