1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-29 12:50:55 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
a691faf876 Merge branch 'WEI_with_questions' into 'main'
Wei with questions

See merge request bde/nk20!218
2023-08-31 11:45:21 +02:00
197 changed files with 2161 additions and 2839 deletions

View File

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

2
.gitmodules vendored
View File

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

View File

@ -55,7 +55,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
(env)$ ./manage.py makemigrations
(env)$ ./manage.py migrate
(env)$ ./manage.py loaddata initial
(env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
(env)$ ./manage.py createsuperuser # Création d'un utilisateur initial
```
6. Enjoy :

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
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
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
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
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
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
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
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
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):

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
from django.utils import timezone

View File

@ -17,27 +17,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</form>
</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 %}
{% endblock %}

View File

@ -46,4 +46,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3>
{% render_table table %}
</div>
{% endblock %}
{% 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
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
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
from hashlib import md5
@ -17,8 +17,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView
from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin
from django_tables2.views import SingleTableView
from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend
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})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
Displays all Activities, and classify if they are on-going or upcoming ones.
"""
model = Activity
tables = [ActivityTable, ActivityTable]
table_class = ActivityTable
ordering = ('-date_start',)
extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs):
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):
context = super().get_context_data(**kwargs)
tables = context["tables"]
for name, table in zip(["table", "upcoming"], tables):
context[name] = table
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
context['upcoming'] = ActivityTable(
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()
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
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
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

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
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
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
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
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
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
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
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
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
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
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."))

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

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

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
import hashlib

View File

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

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,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,32 +253,6 @@ class Club(models.Model):
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):
"""
This function is called each time the club detail view is displayed.
@ -305,6 +273,27 @@ class Club(models.Model):
self._force_save = 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):
"""
@ -344,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):
"""
@ -473,14 +402,66 @@ class Membership(models.Model):
if self.club.parent_club.name == "BDE":
parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all())
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all())
elif self.club.parent_club.name == "Kfet":
parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all())
else:
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.
@ -518,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'])]

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

View File

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

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
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">
{% csrf_token %}
{{ form |crispy }}
{% if user.note.display_image != "pic/default.png" %}
<input type="submit" class="btn btn-primary" value="{% trans "Remove" %}">
{% endif %}
</form>
</div>
<!-- MODAL TO CROP THE IMAGE -->

View File

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

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

View File

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

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

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
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
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')

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
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):

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

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
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
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
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
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."),

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
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")
@ -335,6 +328,11 @@ class SpecialTransaction(Transaction):
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,

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
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
// When a transaction is performed, lock the interface to prevent spam clicks.
@ -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()
});

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
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 = {

View File

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

View File

@ -22,8 +22,8 @@
</p>
<p>
Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde
est inférieur à 0 €.
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
est inférieur à 0 € depuis plus de 24h.
</p>
<p>
@ -43,4 +43,4 @@
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>
</body>
</html>
</html>

View File

@ -9,7 +9,7 @@ Ce mail t'a été envoyé parce que le solde de ta Note Kfet
Ton solde actuel est de {{ note.balance|pretty_money }}.
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
@ -22,4 +22,4 @@ virement bancaire.
--
Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-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

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

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

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
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é
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
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
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
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
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
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"))

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
import sys
from functools import lru_cache

View File

@ -36,7 +36,7 @@
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir son compte utilisateur⋅rice"
"description": "Voir son compte utilisateur"
}
},
{
@ -68,7 +68,7 @@
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir sa propre note d'utilisateur⋅rice"
"description": "Voir sa propre note d'utilisateur"
}
},
{
@ -116,7 +116,7 @@
"mask": 1,
"field": "",
"permanent": false,
"description": "Voir les alias des notes des clubs et des adhérent⋅es du club BDE"
"description": "Voir les aliases des notes des clubs et des adhérents du club BDE"
}
},
{
@ -772,7 +772,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les adhérent⋅es du club"
"description": "Voir les adhérents du club"
}
},
{
@ -788,7 +788,7 @@
"mask": 2,
"field": "",
"permanent": false,
"description": "Ajouter un⋅e membre à un club"
"description": "Ajouter un membre à un club"
}
},
{
@ -852,7 +852,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel⋅le utilisateur⋅rice"
"description": "Modifier n'importe quel utilisateur"
}
},
{
@ -868,7 +868,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter un⋅e utilisateur⋅rice"
"description": "Ajouter un utilisateur"
}
},
{
@ -1284,7 +1284,7 @@
"mask": 2,
"field": "",
"permanent": false,
"description": "Inscrire un⋅e 1A au WEI"
"description": "Inscrire un 1A au WEI"
}
},
{
@ -1956,7 +1956,7 @@
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir mes activités passées, même après la fin de l'adhésion BDE"
"description": "Voir mes activitées passées, même après la fin de l'adhésion BDE"
}
},
{
@ -2100,7 +2100,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir n'importe quel⋅le utilisateur⋅rice"
"description": "Voir n'importe quel utilisateur"
}
},
{
@ -2228,7 +2228,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Créer une note d'utilisateur⋅rice"
"description": "Créer une note d'utilisateur"
}
},
{
@ -2276,7 +2276,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toustes les adhérent⋅es de tous les clubs"
"description": "Voir tous les adhérents de tous les clubs"
}
},
{
@ -2292,7 +2292,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter un⋅e membre à n'importe quel club"
"description": "Ajouter un membre à n'importe quel club"
}
},
{
@ -2372,7 +2372,7 @@
"mask": 1,
"field": "name",
"permanent": false,
"description": "Modifier le nom d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier le nom d'une activité non validée dont on est l'auteur"
}
},
{
@ -2388,7 +2388,7 @@
"mask": 1,
"field": "description",
"permanent": false,
"description": "Modifier la description d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier la description d'une activité non validée dont on est l'auteur"
}
},
{
@ -2404,7 +2404,7 @@
"mask": 1,
"field": "location",
"permanent": false,
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur"
}
},
{
@ -2420,7 +2420,7 @@
"mask": 1,
"field": "activity_type",
"permanent": false,
"description": "Modifier le type d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier le type d'une activité non validée dont on est l'auteur"
}
},
{
@ -2436,7 +2436,7 @@
"mask": 1,
"field": "organizer",
"permanent": false,
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur"
}
},
{
@ -2452,7 +2452,7 @@
"mask": 1,
"field": "attendees_club",
"permanent": false,
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur"
}
},
{
@ -2468,7 +2468,7 @@
"mask": 1,
"field": "date_start",
"permanent": false,
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur"
}
},
{
@ -2484,7 +2484,7 @@
"mask": 1,
"field": "date_end",
"permanent": false,
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur⋅rice"
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur"
}
},
{
@ -2591,12 +2591,12 @@
"note",
"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",
"mask": 2,
"field": "",
"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 €"
}
},
{
@ -2756,7 +2756,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
"description": "Modifier n'importe quel utilisateur non encore inscrit"
}
},
{
@ -2788,7 +2788,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir tous les alias, y compris ceux des non adhérent⋅es"
"description": "Voir tous les alias, y compris ceux des non adhérents"
}
},
{
@ -2820,7 +2820,7 @@
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
"description": "Voir n'importe quel utilisateur non encore inscrit"
}
},
{
@ -2847,12 +2847,12 @@
"auth",
"user"
],
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent⋅e BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e BDE"
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
}
},
{
@ -3044,7 +3044,7 @@
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toutes les amitiés, y compris celles des non adhérent⋅es"
"description": "Voir toutes les amitiés, y compris celles des non adhérents"
}
},
{
@ -3073,7 +3073,7 @@
],
"query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]",
"type": "add",
"mask": 1,
"mask": 2,
"field": "",
"permanent": false,
"description": "Transférer de l'argent depuis une note amie en restant positif"
@ -3093,22 +3093,6 @@
"field": "",
"permanent": false,
"description": "Créer un crédit quelconque"
}
},
{
"model": "permission.permission",
"pk": 198,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusted__noteuser__user\": [\"user\"]}",
"type": "view",
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir ceux nous ayant pour ami, pour toujours"
}
},
{
@ -3116,7 +3100,7 @@
"pk": 1,
"fields": {
"for_club": 1,
"name": "Adh\u00e9rent\u22c5e BDE",
"name": "Adh\u00e9rent BDE",
"permissions": [
1,
2,
@ -3148,11 +3132,10 @@
187,
188,
189,
190,
191,
195,
196,
198
190,
191,
195,
196
]
}
},
@ -3161,7 +3144,7 @@
"pk": 2,
"fields": {
"for_club": 2,
"name": "Adh\u00e9rent\u22c5e Kfet",
"name": "Adh\u00e9rent Kfet",
"permissions": [
22,
36,
@ -3225,11 +3208,10 @@
"pk": 5,
"fields": {
"for_club": null,
"name": "Pr\u00e9sident\u22c5e de club",
"name": "Pr\u00e9sident\u00b7e de club",
"permissions": [
62,
142,
135
142
]
}
},
@ -3238,7 +3220,7 @@
"pk": 6,
"fields": {
"for_club": null,
"name": "Tr\u00e9sorièr\u22c5e de club",
"name": "Tr\u00e9sorier\u00b7\u00e8re de club",
"permissions": [
19,
20,
@ -3262,7 +3244,7 @@
"pk": 7,
"fields": {
"for_club": 1,
"name": "Pr\u00e9sident\u22c5e BDE",
"name": "Pr\u00e9sident\u00b7e BDE",
"permissions": [
24,
25,
@ -3277,8 +3259,6 @@
136,
137,
150,
163,
164,
166,
167,
168,
@ -3291,7 +3271,7 @@
"pk": 8,
"fields": {
"for_club": 1,
"name": "Tr\u00e9sorièr\u22c5e BDE",
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE",
"permissions": [
23,
24,
@ -3459,7 +3439,7 @@
"pk": 13,
"fields": {
"for_club": null,
"name": "Chef\u22c5fe de bus",
"name": "Chef de bus",
"permissions": [
22,
84,
@ -3478,7 +3458,7 @@
"pk": 14,
"fields": {
"for_club": null,
"name": "Chef\u22c5fe d'\u00e9quipe",
"name": "Chef d'\u00e9quipe",
"permissions": [
22,
84,
@ -3527,7 +3507,7 @@
"pk": 18,
"fields": {
"for_club": null,
"name": "Adhérent\u22c5e WEI",
"name": "Adhérent WEI",
"permissions": [
77,
114
@ -3598,20 +3578,6 @@
]
}
},
{
"model": "permission.role",
"pk": 21,
"fields": {
"for_club": 1,
"name": "GC anti-VSS",
"permissions": [
150,
163,
164,
182
]
}
},
{
"model": "wei.weirole",
"pk": 12,

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
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):
"""
@ -343,9 +344,9 @@ class Role(models.Model):
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

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
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
from oauth2_provider.oauth2_validators import OAuth2Validator
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
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
import django_tables2 as tables
@ -36,8 +36,8 @@ class RightsTable(tables.Table):
def render_roles(self, record):
# If the user has the right to manage the roles, display the link to manage them
roles = record.roles.filter((~(Q(name="Adhérent⋅e BDE")
| Q(name="Adhérent⋅e Kfet")
roles = record.roles.filter((~(Q(name="Adhérent BDE")
| Q(name="Adhérent Kfet")
| Q(name="Membre de club")
| Q(name="Bureau de club"))
& Q(weirole__isnull=True))).all()

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
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
from datetime import timedelta
@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase):
# Create membership to validate permissions
NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
membership.save()
# User is now a member and can now see its own user detail
@ -85,7 +85,7 @@ class OAuth2TestCase(TestCase):
bde = Club.objects.get(name="BDE")
NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
membership.save()
resp = self.client.get(reverse('permission:scopes'))

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

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

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