Compare commits

..

2 Commits

Author SHA1 Message Date
hugoooo b2a04096ef Merge branch 'summary_notes' into 'main'
treasury summary

See merge request bde/nk20!223
2023-11-19 15:07:14 +01:00
Hugo 09d1b65987 treasury summary 2023-10-05 16:54:45 +02:00
165 changed files with 1436 additions and 1835 deletions

View File

@ -8,19 +8,19 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Debian Buster # Debian Buster
# py37-django22: py37-django22:
# stage: test stage: test
# image: debian:buster-backports image: debian:buster-backports
# before_script: before_script:
# - > - >
# apt-get update && apt-get update &&
# apt-get install --no-install-recommends -t buster-backports -y apt-get install --no-install-recommends -t buster-backports -y
# python3-django python3-django-crispy-forms python3-django python3-django-crispy-forms
# python3-django-extensions python3-django-filters python3-django-polymorphic python3-django-extensions python3-django-filters python3-django-polymorphic
# python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
# python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
# python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
# script: tox -e py37-django22 script: tox -e py37-django22
# Ubuntu 20.04 # Ubuntu 20.04
py38-django22: py38-django22:
@ -56,7 +56,7 @@ py39-django22:
linters: linters:
stage: quality-assurance stage: quality-assurance
image: debian:bullseye image: debian:buster-backports
before_script: before_script:
- apt-get update && apt-get install -y tox - apt-get update && apt-get install -y tox
script: tox -e linters script: tox -e linters

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,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'),
),
]

View File

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

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone from django.utils import timezone

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from hashlib import md5 from hashlib import md5
@ -17,8 +17,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView from django.views.generic import DetailView, TemplateView, UpdateView
from django.views.generic.list import ListView from django_tables2.views import SingleTableView
from django_tables2.views import MultiTableMixin
from note.models import Alias, NoteSpecial, NoteUser from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
@ -58,40 +57,26 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
Displays all Activities, and classify if they are on-going or upcoming ones. Displays all Activities, and classify if they are on-going or upcoming ones.
""" """
model = Activity model = Activity
tables = [ActivityTable, ActivityTable] table_class = ActivityTable
ordering = ('-date_start',)
extra_context = {"title": _("Activities")} extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct() return super().get_queryset(**kwargs).distinct()
def get_tables(self):
tables = super().get_tables()
tables[0].prefix = "all-"
tables[1].prefix = "upcoming-"
return tables
def get_tables_data(self):
# first table = all activities, second table = upcoming
return [
self.get_queryset().order_by("-date_start"),
Activity.objects.filter(date_end__gt=timezone.now())
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
.distinct()
.order_by("date_start")
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
tables = context["tables"] upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
for name, table in zip(["table", "upcoming"], tables): context['upcoming'] = ActivityTable(
context[name] = table data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
prefix='upcoming-',
)
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
context["started_activities"] = started_activities context["started_activities"] = started_activities

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import io import io
@ -121,7 +121,7 @@ class ImageForm(forms.Form):
frame = frame.crop((x, y, x + w, y + h)) frame = frame.crop((x, y, x + w, y + h))
frame = frame.resize( frame = frame.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH), (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.LANCZOS, Image.ANTIALIAS,
) )
frames.append(frame) frames.append(frame)
@ -138,9 +138,6 @@ class ImageForm(forms.Form):
return cleaned_data return cleaned_data
def is_valid(self):
return super().is_valid() or super().clean().get('image') is None
class ClubForm(forms.ModelForm): class ClubForm(forms.ModelForm):
def clean(self): def clean(self):
@ -154,7 +151,7 @@ class ClubForm(forms.ModelForm):
class Meta: class Meta:
model = Club model = Club
exclude = ("add_registration_form",) fields = '__all__'
widgets = { widgets = {
"membership_fee_paid": AmountInput(), "membership_fee_paid": AmountInput(),
"membership_fee_unpaid": AmountInput(), "membership_fee_unpaid": AmountInput(),

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import hashlib import hashlib

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2024-07-15 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0011_profile_vss_charter_read'),
]
operations = [
migrations.AddField(
model_name='club',
name='add_registration_form',
field=models.BooleanField(default=False, verbose_name='add to registration form'),
),
]

View File

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

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import datetime import datetime
@ -28,6 +28,7 @@ class Profile(models.Model):
We do not want to patch the Django Contrib :model:`auth.User`model; We do not want to patch the Django Contrib :model:`auth.User`model;
so this model add an user profile with additional information. so this model add an user profile with additional information.
""" """
user = models.OneToOneField( user = models.OneToOneField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -138,17 +139,6 @@ class Profile(models.Model):
default=False default=False
) )
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return str(self.user)
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
@property @property
def ens_year(self): def ens_year(self):
""" """
@ -173,6 +163,17 @@ class Profile(models.Model):
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
return False return False
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
def __str__(self):
return str(self.user)
def send_email_validation_link(self): def send_email_validation_link(self):
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
token = email_validation_token.make_token(self.user) token = email_validation_token.make_token(self.user)
@ -204,11 +205,9 @@ class Club(models.Model):
max_length=255, max_length=255,
unique=True, unique=True,
) )
email = models.EmailField( email = models.EmailField(
verbose_name=_('email'), verbose_name=_('email'),
) )
parent_club = models.ForeignKey( parent_club = models.ForeignKey(
'self', 'self',
null=True, null=True,
@ -259,32 +258,6 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'), help_text=_('Maximal date of a membership, after which members must renew it.'),
) )
add_registration_form = models.BooleanField(
verbose_name=_("add to registration form"),
default=False,
)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.require_memberships:
self.membership_fee_paid = 0
self.membership_fee_unpaid = 0
self.membership_duration = None
self.membership_start = None
self.membership_end = None
super().save(force_insert, force_update, update_fields)
def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk,))
def update_membership_dates(self): def update_membership_dates(self):
""" """
This function is called each time the club detail view is displayed. This function is called each time the club detail view is displayed.
@ -305,6 +278,27 @@ class Club(models.Model):
self._force_save = True self._force_save = True
self.save(force_update=True) self.save(force_update=True)
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.require_memberships:
self.membership_fee_paid = 0
self.membership_fee_unpaid = 0
self.membership_duration = None
self.membership_start = None
self.membership_end = None
super().save(force_insert, force_update, update_fields)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk,))
class Membership(models.Model): class Membership(models.Model):
""" """
@ -344,66 +338,6 @@ class Membership(models.Model):
verbose_name=_('fee'), verbose_name=_('fee'),
) )
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
@property @property
def valid(self): def valid(self):
""" """
@ -481,6 +415,58 @@ class Membership(models.Model):
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() parent_membership.save()
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
def make_transaction(self): def make_transaction(self):
""" """
Create Membership transaction associated to this membership. Create Membership transaction associated to this membership.
@ -518,3 +504,11 @@ class Membership(models.Model):
soge_credit.save() soge_credit.save()
else: else:
transaction.save(force_insert=True) transaction.save(force_insert=True)
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]

View File

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

View File

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

View File

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

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, date from datetime import timedelta, date
@ -26,7 +26,7 @@ from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
CustomAuthenticationForm, MembershipRolesForm CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -326,9 +326,6 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
"""Save image to note""" """Save image to note"""
image = form.cleaned_data['image'] image = form.cleaned_data['image']
if image is None:
image = "pic/default.png"
else:
# Rename as a PNG or GIF # Rename as a PNG or GIF
extension = image.name.split(".")[-1] extension = image.name.split(".")[-1]
if extension == "gif": if extension == "gif":

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'note.apps.NoteConfig' default_app_config = 'note.apps.NoteConfig'

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import re import re
@ -13,7 +13,7 @@ from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
TrustSerializer TrustSerializer
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (C) 2018-2024 by BDE ENS Paris-Saclay // Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// When a transaction is performed, lock the interface to prevent spam clicks. // When a transaction is performed, lock the interface to prevent spam clicks.

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import html import html

View File

@ -23,7 +23,7 @@
<p> <p>
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
est inférieur à 0 €. est inférieur à 0 € depuis plus de 24h.
</p> </p>
<p> <p>

View File

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

View File

@ -13,47 +13,47 @@
</h1> </h1>
<h2> <h2>
Tous les utilisateur⋅rices : Tous les utilisateurs :
</h2> </h2>
<ul> <ul>
<li>Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} €</li> <li>Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user }} euros</li>
<li>Neutres : {{ summary.total_zero_user }}</li> <li>Neutres : {{ summary.total_zero_user }}</li>
<li>Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} €</li> <li>Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user }} euros</li>
</ul> </ul>
<h2> <h2>
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE : Les adhérents BDE :
</h2> </h2>
<ul> <ul>
<li>Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} €</li> <li>Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde }} euros</li>
<li>Neutres : {{ summary.total_zero_user_bde }}</li> <li>Neutres : {{ summary.total_zero_user_bde }}</li>
<li>Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde / 100 }} €</li> <li>Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde }} euros</li>
</ul> </ul>
<h2> <h2>
Clubs : Clubs :
</h2> </h2>
<ul> <ul>
<li>Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} €</li> <li>Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club }} euros</li>
<li>Neutres : {{ summary.total_zero_club }}</li> <li>Neutres : {{ summary.total_zero_club }}</li>
<li>Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} €</li> <li>Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club }} euros</li>
</ul> </ul>
<h2> <h2>
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" : Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
</h2> </h2>
<ul> <ul>
<li>Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} €</li> <li>Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde }} euros</li>
<li>Neutres : {{ summary.total_zero_club_nbde }}</li> <li>Neutres : {{ summary.total_zero_club_nbde }}</li>
<li>Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} €</li> <li>Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde }} euros</li>
</ul> </ul>
<h2> <h2>
Progression : Progression:
</h2> </h2>
<ul> <ul>
<li>Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices</li> <li>Ceci correspond à une différence de {{ balance_difference_user }} euros pour les utilisateurs</li>
<li>Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs</li> <li>Ceci correspond à une différence de {{ balance_difference_club }} euros pour les clubs</li>
</ul> </ul>
-- --

View File

@ -3,29 +3,29 @@
Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} : Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} :
Tous les utilisateur⋅rices : Tous les utilisateurs :
- Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} € - Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user }} euros
- Neutres : {{ summary.total_zero_user }} - Neutres : {{ summary.total_zero_user }}
- Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} € - Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user }} euros
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE : Les adhérents BDE :
- Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} € - Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde }} euros
- Neutres : {{ summary.total_zero_user_bde }} - Neutres : {{ summary.total_zero_user_bde }}
- Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde /100 }} € - Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde }} euros
Clubs : Clubs :
- Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} € - Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club }} euros
- Neutres : {{ summary.total_zero_club }} - Neutres : {{ summary.total_zero_club }}
- Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} € - Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club }} euros
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" : Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
- Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} € - Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde }} euros
- Neutres : {{ summary.total_zero_club_nbde }} - Neutres : {{ summary.total_zero_club_nbde }}
- Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} € - Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde }} euros
Progression : Progression:
- Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices - Ceci correspond à une différence de {{ balance_difference_user }} euros pour les utilisateurs
- Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs - Ceci correspond à une différence de {{ balance_difference_club }} euros pour les clubs
-- --
Le BDE Le BDE

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from django import template from django import template

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2591,12 +2591,12 @@
"note", "note",
"transaction" "transaction"
], ],
"query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]", "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]",
"type": "add", "type": "add",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€" "description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €"
} }
}, },
{ {
@ -3228,8 +3228,7 @@
"name": "Pr\u00e9sident\u00b7e de club", "name": "Pr\u00e9sident\u00b7e de club",
"permissions": [ "permissions": [
62, 62,
142, 142
135
] ]
} }
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,11 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -114,3 +115,12 @@ class ValidationForm(forms.Form):
required=False, required=False,
initial=True, initial=True,
) )
# If the bda exists
if Club.objects.filter(name__iexact="bda").exists():
# The user can join the bda club at the inscription
join_bda = forms.BooleanField(
label=_("Join BDA Club"),
required=False,
initial=True,
)

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import django_tables2 as tables import django_tables2 as tables

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py # Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path

View File

@ -1,7 +1,6 @@
# 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 # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -239,8 +238,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet") kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
for club in Club.objects.filter(add_registration_form=True): if Club.objects.filter(name__iexact="BDA").exists():
fee += club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid bda = Club.objects.get(name__iexact="BDA")
fee += bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, ) ctx["total_fee"] = "{:.02f}".format(fee / 100, )
# ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() # ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
@ -249,16 +249,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
# add clubs that are in registration form
for club in Club.objects.filter(add_registration_form=True).order_by("name"):
form_join_club = forms.BooleanField(
label=_("Join %(club)s Club") % {'club': club.name},
required=False,
initial=False,
)
form.fields.update({f"join_{club.id}": form_join_club})
user = self.get_object() user = self.get_object()
form.fields["last_name"].initial = user.last_name form.fields["last_name"].initial = user.last_name
form.fields["first_name"].initial = user.first_name form.fields["first_name"].initial = user.first_name
@ -276,6 +266,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.add_error(None, _("An alias with a similar name already exists.")) form.add_error(None, _("An alias with a similar name already exists."))
return self.form_invalid(form) return self.form_invalid(form)
# Check if BDA exist to propose membership at regisration
bda_exists = False
if Club.objects.filter(name__iexact="BDA").exists():
bda_exists = True
# Get form data # Get form data
# soge = form.cleaned_data["soge"] # soge = form.cleaned_data["soge"]
credit_type = form.cleaned_data["credit_type"] credit_type = form.cleaned_data["credit_type"]
@ -285,9 +280,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
bank = form.cleaned_data["bank"] bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"] join_bde = form.cleaned_data["join_bde"]
join_kfet = form.cleaned_data["join_kfet"] join_kfet = form.cleaned_data["join_kfet"]
if bda_exists:
clubs_registration = Club.objects.filter(add_registration_form=True).order_by("name") join_bda = form.cleaned_data["join_bda"]
join_clubs = [(club, form.cleaned_data[f"join_{club.id}"]) for club in clubs_registration]
# if soge: # if soge:
# # If Société Générale pays the inscription, the user automatically joins the two clubs. # # If Société Générale pays the inscription, the user automatically joins the two clubs.
@ -309,12 +303,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
# Add extra fee for the full membership # Add extra fee for the full membership
fee += kfet_fee if join_kfet else 0 fee += kfet_fee if join_kfet else 0
clubs_fee = dict() if bda_exists:
for club, join_club in join_clubs: bda = Club.objects.get(name__iexact="BDA")
club_fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid bda_fee = bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
# Add extra fee for the club membership # Add extra fee for the bda membership
clubs_fee[club] = club_fee fee += bda_fee if join_bda else 0
fee += club_fee if join_club else 0
# # If the bank pays, then we don't credit now. Treasurers will validate the transaction # # If the bank pays, then we don't credit now. Treasurers will validate the transaction
# # and credit the note later. # # and credit the note later.
@ -394,13 +387,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save() membership.save()
for club, join_club in join_clubs: if bda_exists and join_bda:
if join_club:
# Create membership for the user to the BDA starting today # Create membership for the user to the BDA starting today
membership = Membership( membership = Membership(
club=club, club=bda,
user=user, user=user,
fee=clubs_fee[club], fee=bda_fee,
) )
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'treasury.apps.TreasuryConfig' default_app_config = 'treasury.apps.TreasuryConfig'

View File

@ -1,17 +1,17 @@
# 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é # SPDX-License-Identifier: GPL-3.0-or-lateré
from django.contrib import admin from django.contrib import admin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .forms import ProductForm from .forms import ProductForm
from .models import Invoice, NoteSummary, Product, RemittanceType, Remittance, SogeCredit from .models import RemittanceType, Remittance, SogeCredit, Invoice, Product
@admin.register(RemittanceType, site=admin_site) @admin.register(RemittanceType, site=admin_site)
class RemittanceTypeAdmin(admin.ModelAdmin): class RemittanceTypeAdmin(admin.ModelAdmin):
""" """
Admin customisation for RemittanceType Admin customisation for RemiitanceType
""" """
list_display = ('note', ) list_display = ('note', )
@ -55,19 +55,3 @@ class InvoiceAdmin(admin.ModelAdmin):
""" """
list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',) list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',)
inlines = (ProductInline,) inlines = (ProductInline,)
@admin.register(NoteSummary, site=admin_site)
class NoteSummaryAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteSummary
"""
list_display = (
'date', 'total_positive_user', 'balance_positive_user', 'total_positive_user_bde',
'balance_positive_user_bde', 'total_zero_user', 'total_zero_user_bde', 'total_negative_user',
'balance_negative_user', 'total_negative_user_bde', 'balance_negative_user_bde',
'total_vnegative_user', 'balance_vnegative_user', 'total_vnegative_user_bde',
'balance_vnegative_user_bde', 'total_positive_club', 'balance_positive_club',
'total_positive_club_nbde', 'balance_positive_club_nbde', 'total_zero_club', 'total_zero_club_nbde',
'total_negative_club', 'balance_negative_club', 'total_negative_club_nbde', 'balance_negative_club_nbde',
)

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