mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 18:08:21 +02:00
Compare commits
117 Commits
VSS
...
traduction
Author | SHA1 | Date | |
---|---|---|---|
354c79bb82 | |||
d595d908c6 | |||
734f5b242d | |||
b0c7d43a50 | |||
b8f81048a5 | |||
af819f45a1 | |||
076d065ffa | |||
2da77d9c17 | |||
01584d6330 | |||
4c0a5922c4 | |||
f90b28fc7c | |||
925e0f26f5 | |||
c912383f86 | |||
32830e43fd | |||
11c6a6fa7a | |||
201d6b114a | |||
19e77df299 | |||
5fd6ec5668 | |||
10a01c5bc2 | |||
989905ea64 | |||
0218d43a17 | |||
5d30b0e819 | |||
ec759dd3c0 | |||
2eb965291d | |||
7f182ee2ee | |||
3132aa4c38 | |||
c7eb774859 | |||
32f8d285b3 | |||
050256ea13 | |||
7afd15b1cc | |||
258361f116 | |||
a307530579 | |||
5de930bf40 | |||
f7ebe0e99b | |||
73de6e2176 | |||
201611b105 | |||
40c239e9da | |||
2aaab2b454 | |||
fc088dec86 | |||
2d60f1fd7b | |||
7b48b09329 | |||
ffac940511 | |||
50f98fd5ad | |||
402e19d1ce | |||
0b0394b61f | |||
98422d8259 | |||
29509b5b26 | |||
0d64ad31e0 | |||
5781cbd6a5 | |||
5295e61a00 | |||
e79ed6226a | |||
68152e6354 | |||
b8cc297baf | |||
cd8224f2e0 | |||
3c882a7854 | |||
357e1bbaa2 | |||
f5c4c58525 | |||
dafb602b08 | |||
5b377e6a75 | |||
28bd62531e | |||
b3a31c27a5 | |||
c7a8e6a1a5 | |||
546a3a72b1 | |||
2e5664f79d | |||
e367666fe9 | |||
04a9b3daf0 | |||
d1df8f3eac | |||
a5221f66ef | |||
7d59cd6cd2 | |||
0db0474217 | |||
2b3eb15f59 | |||
a6b479db19 | |||
048d251f75 | |||
7b11cb0797 | |||
ff3c30517e | |||
f481ea6acb | |||
802fd8c2d7 | |||
5209a586a9 | |||
24f54ac876 | |||
988b4c9e88 | |||
e32c267995 | |||
5e39209ab1 | |||
08b2fabe07 | |||
405479e5ad | |||
0cc130092f | |||
ff6e207512 | |||
0f1e4d2e60 | |||
6255bcbbb1 | |||
d82a1001c4 | |||
31a54482f0 | |||
4ee02345d4 | |||
422c087d17 | |||
30d6e2c95e | |||
f3a3f07e38 | |||
a5e802f370 | |||
540f3bc354 | |||
2d19457506 | |||
72786d0d2b | |||
f099cbc879 | |||
977eb7c0d4 | |||
d81b1f2710 | |||
6a69590a82 | |||
7afc583282 | |||
4fb0b7d736 | |||
18a5b65a1c | |||
6bc52be707 | |||
834d68fe35 | |||
c6a2849d35 | |||
4ab22c92b3 | |||
c328c1457c | |||
96da7d01ae | |||
d27f942339 | |||
738d6c932d | |||
1760196578 | |||
13b9b6edea | |||
e06e3b2972 | |||
9596aa7b8c |
@ -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:buster-backports
|
||||
image: debian:bullseye
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y tox
|
||||
script: tox -e linters
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "apps/scripts"]
|
||||
path = apps/scripts
|
||||
url = https://gitlab.crans.org/bde/nk20-scripts.git
|
||||
url = https://gitlab.crans.org/bde/nk20-scripts
|
||||
|
@ -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 utilisateur initial
|
||||
(env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
|
||||
```
|
||||
|
||||
6. Enjoy :
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'activity.apps.ActivityConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta
|
||||
|
18
apps/activity/migrations/0003_auto_20240323_1422.py
Normal file
18
apps/activity/migrations/0003_auto_20240323_1422.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2024-03-23 13:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('activity', '0002_auto_20200904_2341'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default='', verbose_name='description'),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
@ -66,6 +66,8 @@ class Activity(models.Model):
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_('description'),
|
||||
blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
location = models.CharField(
|
||||
@ -123,6 +125,14 @@ 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):
|
||||
"""
|
||||
@ -144,14 +154,6 @@ 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):
|
||||
"""
|
||||
@ -252,14 +254,13 @@ class Guest(models.Model):
|
||||
verbose_name=_("inviter"),
|
||||
)
|
||||
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
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
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
@ -290,13 +291,14 @@ class Guest(models.Model):
|
||||
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
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", )
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
class GuestTransaction(Transaction):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils import timezone
|
||||
|
@ -18,3 +18,26 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
var date_end = document.getElementById("id_date_end");
|
||||
var date_start = document.getElementById("id_date_start");
|
||||
|
||||
function update_date_end (){
|
||||
if(date_end.value=="" || date_end.value<date_start.value){
|
||||
date_end.value = date_start.value;
|
||||
};
|
||||
};
|
||||
|
||||
function update_date_start (){
|
||||
if(date_start.value=="" || date_end.value<date_start.value){
|
||||
date_start.value = date_end.value;
|
||||
};
|
||||
};
|
||||
|
||||
date_start.addEventListener('focusout', update_date_end);
|
||||
date_end.addEventListener('focusout', update_date_start);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from hashlib import md5
|
||||
@ -17,7 +17,8 @@ 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_tables2.views import SingleTableView
|
||||
from django.views.generic.list import ListView
|
||||
from django_tables2.views import MultiTableMixin
|
||||
from note.models import Alias, NoteSpecial, NoteUser
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
@ -57,26 +58,40 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
|
||||
|
||||
|
||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
|
||||
"""
|
||||
Displays all Activities, and classify if they are on-going or upcoming ones.
|
||||
"""
|
||||
model = Activity
|
||||
table_class = ActivityTable
|
||||
ordering = ('-date_start',)
|
||||
tables = [ActivityTable, ActivityTable]
|
||||
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)
|
||||
|
||||
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-',
|
||||
)
|
||||
tables = context["tables"]
|
||||
for name, table in zip(["table", "upcoming"], tables):
|
||||
context[name] = table
|
||||
|
||||
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
||||
context["started_activities"] = started_activities
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'api.apps.APIConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'logs.apps.LogsConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ChangelogViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
@ -76,9 +76,6 @@ 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")
|
||||
@ -86,3 +83,6 @@ class Changelog(models.Model):
|
||||
def __str__(self):
|
||||
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
||||
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 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 utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||
# Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
|
||||
request = get_current_request()
|
||||
|
||||
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 dans la VM doit être un des alias note du respo info
|
||||
# IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
|
||||
ip = "127.0.0.1"
|
||||
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 utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||
# Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
|
||||
request = get_current_request()
|
||||
|
||||
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 dans la VM doit être un des alias note du respo info
|
||||
# IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
|
||||
ip = "127.0.0.1"
|
||||
username = Alias.normalize(getpass.getuser())
|
||||
note = NoteUser.objects.filter(alias__normalized_name=username)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'member.apps.MemberConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import io
|
||||
@ -121,7 +121,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.ANTIALIAS,
|
||||
Image.LANCZOS,
|
||||
)
|
||||
frames.append(frame)
|
||||
|
||||
@ -138,6 +138,9 @@ 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):
|
||||
@ -151,7 +154,7 @@ class ClubForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Club
|
||||
fields = '__all__'
|
||||
exclude = ("add_registration_form",)
|
||||
widgets = {
|
||||
"membership_fee_paid": AmountInput(),
|
||||
"membership_fee_unpaid": AmountInput(),
|
||||
@ -207,9 +210,9 @@ class MembershipForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ('user', 'date_start')
|
||||
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
|
||||
# Le champ d'utilisateur⋅rice est remplacé par un champ d'auto-complétion.
|
||||
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
|
||||
# et récupère les noms d'utilisateur valides
|
||||
# et récupère les noms d'utilisateur⋅rices valides
|
||||
widgets = {
|
||||
'user':
|
||||
Autocomplete(
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
|
18
apps/member/migrations/0012_club_add_registration_form.py
Normal file
18
apps/member/migrations/0012_club_add_registration_form.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2024-07-15 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0011_profile_vss_charter_read'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='club',
|
||||
name='add_registration_form',
|
||||
field=models.BooleanField(default=False, verbose_name='add to registration form'),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import datetime
|
||||
@ -28,7 +28,6 @@ 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,
|
||||
@ -139,6 +138,17 @@ class Profile(models.Model):
|
||||
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):
|
||||
"""
|
||||
@ -163,17 +173,6 @@ 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)
|
||||
@ -205,9 +204,11 @@ class Club(models.Model):
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
verbose_name=_('email'),
|
||||
)
|
||||
|
||||
parent_club = models.ForeignKey(
|
||||
'self',
|
||||
null=True,
|
||||
@ -258,6 +259,32 @@ 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.
|
||||
@ -278,27 +305,6 @@ 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):
|
||||
"""
|
||||
@ -338,6 +344,66 @@ 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):
|
||||
"""
|
||||
@ -407,66 +473,14 @@ class Membership(models.Model):
|
||||
|
||||
if self.club.parent_club.name == "BDE":
|
||||
parent_membership.roles.set(
|
||||
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all())
|
||||
Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all())
|
||||
elif self.club.parent_club.name == "Kfet":
|
||||
parent_membership.roles.set(
|
||||
Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all())
|
||||
Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
|
||||
else:
|
||||
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.
|
||||
@ -504,11 +518,3 @@ class Membership(models.Model):
|
||||
soge_credit.save()
|
||||
else:
|
||||
transaction.save(force_insert=True)
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('membership')
|
||||
verbose_name_plural = _('memberships')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* On form submit, create a new friendship
|
||||
*/
|
||||
function create_trust (e) {
|
||||
function form_create_trust (e) {
|
||||
// Do not submit HTML form
|
||||
e.preventDefault()
|
||||
|
||||
@ -14,25 +14,35 @@ function create_trust (e) {
|
||||
addMsg(gettext("You can't add yourself as a friend"), "danger")
|
||||
return
|
||||
}
|
||||
$.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)
|
||||
})
|
||||
create_trust(formData.get('trusting'), trusted_alias.note)
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the alias
|
||||
* @param button_id:Integer Alias id to remove
|
||||
* 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
|
||||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
@ -42,6 +52,7 @@ 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)
|
||||
})
|
||||
@ -49,5 +60,5 @@ function delete_button (button_id) {
|
||||
|
||||
$(document).ready(function () {
|
||||
// Attach event
|
||||
document.getElementById('form_trust').addEventListener('submit', create_trust)
|
||||
document.getElementById('form_trust').addEventListener('submit', form_create_trust)
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
@ -14,6 +14,9 @@ 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 -->
|
||||
|
@ -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 "Note friendships" %}
|
||||
{% trans "Add friends" %}
|
||||
</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">
|
||||
<div class="alert alert-warning card mb-3">
|
||||
{% blocktrans trimmed %}
|
||||
Adding someone as a friend enables them to initiate transactions coming
|
||||
from your account (while keeping your balance positive). This is
|
||||
@ -33,6 +33,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
friends without needing additional rights among them.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "People having you as a friend" %}
|
||||
</h3>
|
||||
{% render_table trusted_by %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
@ -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ésorier·ère de club") | Q(name="Bureau de club")).all()],
|
||||
Q(name="Membre de club") | Q(name="Trésorièr⋅e de club") | Q(name="Bureau de club")).all()],
|
||||
))
|
||||
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
|
||||
self.membership.refresh_from_db()
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta, date
|
||||
@ -8,7 +8,6 @@ from django.contrib.auth import logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.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
|
||||
@ -21,13 +20,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
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
|
||||
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
|
||||
@ -258,17 +257,18 @@ 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,6 +326,9 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
|
||||
"""Save image to note"""
|
||||
image = form.cleaned_data['image']
|
||||
|
||||
if image is None:
|
||||
image = "pic/default.png"
|
||||
else:
|
||||
# Rename as a PNG or GIF
|
||||
extension = image.name.split(".")[-1]
|
||||
if extension == "gif":
|
||||
@ -824,8 +827,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
ret = super().form_valid(form)
|
||||
|
||||
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() \
|
||||
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() \
|
||||
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
||||
# Set the same roles as before
|
||||
if old_membership:
|
||||
@ -861,7 +864,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 Kfet") | Q(name="Membre de club")).all())
|
||||
membership.roles.set(Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
|
||||
membership.save()
|
||||
|
||||
return ret
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'note.apps.NoteConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
|
||||
RecurrentTransaction, MembershipTransaction, SpecialTransaction
|
||||
from .templatetags.pretty_money import pretty_money
|
||||
@ -21,6 +21,16 @@ 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):
|
||||
"""
|
||||
@ -92,7 +102,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||
"""
|
||||
Child for an user note, see NoteAdmin
|
||||
"""
|
||||
inlines = (AliasInlines,)
|
||||
inlines = (AliasInlines, TrustInlines)
|
||||
|
||||
# We can't change user after creation or the balance
|
||||
readonly_fields = ('user', 'balance')
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
@ -11,6 +11,7 @@ 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, \
|
||||
@ -86,11 +87,9 @@ class TrustSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Trust
|
||||
fields = '__all__'
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = Trust(**attrs)
|
||||
instance.clean()
|
||||
return attrs
|
||||
validators = [UniqueTogetherValidator(
|
||||
queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
|
||||
message=_("This friendship already exists"))]
|
||||
|
||||
|
||||
class AliasSerializer(serializers.ModelSerializer):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import re
|
||||
|
||||
@ -13,7 +13,7 @@ from rest_framework import status
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
|
||||
TrustSerializer
|
||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import unicodedata
|
||||
@ -293,6 +293,11 @@ 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):
|
||||
"""
|
||||
@ -321,11 +326,6 @@ class Alias(models.Model):
|
||||
pass
|
||||
self.normalized_name = normalized_name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.name == str(self.note):
|
||||
raise ValidationError(_("You can't delete your main alias."),
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
|
||||
amount = models.PositiveIntegerField(
|
||||
verbose_name=_('amount'),
|
||||
)
|
||||
|
||||
category = models.ForeignKey(
|
||||
TemplateCategory,
|
||||
on_delete=models.PROTECT,
|
||||
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
|
||||
verbose_name = _("transaction template")
|
||||
verbose_name_plural = _("transaction templates")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('note:template_update', args=(self.pk,))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('note:template_update', args=(self.pk,))
|
||||
|
||||
|
||||
class Transaction(PolymorphicModel):
|
||||
"""
|
||||
@ -101,7 +102,6 @@ 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,6 +166,50 @@ 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
|
||||
@ -208,46 +252,6 @@ 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
|
||||
@ -256,46 +260,40 @@ 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"),
|
||||
@ -312,6 +310,15 @@ 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")
|
||||
@ -328,11 +335,6 @@ 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):
|
||||
"""
|
||||
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
|
||||
|
||||
return not error
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Special transaction")
|
||||
verbose_name_plural = _("Special transactions")
|
||||
|
||||
|
||||
class MembershipTransaction(Transaction):
|
||||
"""
|
||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||
|
||||
"""
|
||||
|
||||
membership = models.OneToOneField(
|
||||
'member.Membership',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils import timezone
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
// Copyright (C) 2018-2024 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,3 +258,39 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var searchbar = document.getElementById("search-input")
|
||||
var search_results = document.getElementById("search-results")
|
||||
|
||||
var old_pattern = null;
|
||||
var firstMatch = null;
|
||||
/**
|
||||
* Updates the button search tab
|
||||
* @param force Forces the update even if the pattern didn't change
|
||||
*/
|
||||
function updateSearch(force = false) {
|
||||
let pattern = searchbar.value
|
||||
if (pattern === "")
|
||||
firstMatch = null;
|
||||
if ((pattern === old_pattern || pattern === "") && !force)
|
||||
return;
|
||||
firstMatch = null;
|
||||
const re = new RegExp(pattern, "i");
|
||||
Array.from(search_results.children).forEach(function(b) {
|
||||
if (re.test(b.innerText)) {
|
||||
b.hidden = false;
|
||||
if (firstMatch === null) {
|
||||
firstMatch = b;
|
||||
}
|
||||
} else
|
||||
b.hidden = true;
|
||||
});
|
||||
}
|
||||
|
||||
searchbar.addEventListener("input", function (e) {
|
||||
debounce(updateSearch)()
|
||||
});
|
||||
searchbar.addEventListener("keyup", function (e) {
|
||||
if (firstMatch && e.key === "Enter")
|
||||
firstMatch.click()
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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,6 +173,46 @@ class TrustTable(tables.Table):
|
||||
verbose_name=_("Delete"),)
|
||||
|
||||
|
||||
class TrustedTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table condensed table-striped',
|
||||
'id': 'trusted_table'
|
||||
}
|
||||
Model = Trust
|
||||
fields = ("trusting",)
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
show_header = False
|
||||
trusting = tables.Column(attrs={
|
||||
'td': {'class': 'text-center', 'width': '100%'}})
|
||||
|
||||
trust_back = tables.Column(
|
||||
verbose_name=_("Trust back"),
|
||||
accessor="pk",
|
||||
attrs={
|
||||
'td': {
|
||||
'class': '',
|
||||
'id': lambda record: "trust_back_" + str(record.pk),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def render_trust_back(self, record):
|
||||
user_note = record.trusted
|
||||
trusting_note = record.trusting
|
||||
if Trust.objects.filter(trusted=trusting_note, trusting=user_note):
|
||||
return ""
|
||||
val = '<button id="'
|
||||
val += str(record.pk)
|
||||
val += '" class="btn btn-success btn-sm text-nowrap" \
|
||||
onclick="create_trust(' + str(record.trusted.pk) + ',' + \
|
||||
str(record.trusting.pk) + ')">'
|
||||
val += str(_("Add back"))
|
||||
val += '</button>'
|
||||
return mark_safe(val)
|
||||
|
||||
|
||||
class AliasTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
|
@ -103,6 +103,11 @@ 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>
|
||||
|
||||
@ -123,6 +128,20 @@ 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>
|
||||
|
||||
@ -163,7 +182,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<script type="text/javascript">
|
||||
{% for button in highlighted %}
|
||||
{% if button.display %}
|
||||
$("#highlighted_button{{ button.id }}").click(function() {
|
||||
document.getElementById("highlighted_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 }}");
|
||||
@ -174,7 +193,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% for category in categories %}
|
||||
{% for button in category.templates_filtered %}
|
||||
{% if button.display %}
|
||||
$("#button{{ button.id }}").click(function() {
|
||||
document.getElementById("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 }}");
|
||||
@ -182,5 +201,15 @@ 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 %}
|
||||
|
@ -22,8 +22,8 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
|
||||
est inférieur à 0 € depuis plus de 24h.
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde
|
||||
est inférieur à 0 €.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -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érents dont le solde
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérent·e·s dont le solde
|
||||
est inférieur à 0 € depuis plus de 24h.
|
||||
|
||||
Si tu ne comprends pas ton solde, tu peux consulter ton historique
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import template
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import template
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.tests import TestAPI
|
||||
@ -10,7 +10,7 @@ from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from permission.models import Role
|
||||
|
||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
|
||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
|
||||
TransactionTemplateViewSet, TransactionViewSet
|
||||
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 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_tables2 import SingleTableView
|
||||
from django.urls import reverse_lazy
|
||||
from django_tables2 import SingleTableView
|
||||
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,6 +190,10 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
).order_by('name').all()
|
||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
||||
|
||||
context['all_buttons'] = TransactionTemplate.objects.filter(
|
||||
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
|
||||
).filter(display=True).order_by('name').all()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'permission.apps.PermissionConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import PermissionViewSet, RoleViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
@ -198,6 +198,41 @@ class PermissionBackend(ModelBackend):
|
||||
def has_module_perms(self, user_obj, app_label):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@memoize
|
||||
def has_model_perm(request, model, type):
|
||||
"""
|
||||
Check is the given user has the permission over a given model for a given action.
|
||||
The result is then memoized.
|
||||
:param request: The current request
|
||||
:param model: The model that the permissions shoud apply
|
||||
:param type: The type of the permissions: view, change, add or delete
|
||||
For view action, it is consider possible if user can view or change the model
|
||||
"""
|
||||
# Requested by a shell
|
||||
if request is None:
|
||||
return False
|
||||
|
||||
user_obj = request.user
|
||||
sess = request.session
|
||||
|
||||
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||
# OAuth2 Authentication
|
||||
user_obj = request.auth.user
|
||||
|
||||
if user_obj is None or user_obj.is_anonymous:
|
||||
return False
|
||||
|
||||
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
if any(PermissionBackend.permissions(request, ct, type)):
|
||||
return True
|
||||
if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_all_permissions(self, user_obj, obj=None):
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
return list(self.permissions(get_current_request(), ct, "view"))
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
|
@ -36,7 +36,7 @@
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"description": "Voir son compte utilisateur"
|
||||
"description": "Voir son compte utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -68,7 +68,7 @@
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"description": "Voir sa propre note d'utilisateur"
|
||||
"description": "Voir sa propre note d'utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -116,7 +116,7 @@
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir les aliases des notes des clubs et des adhérents du club BDE"
|
||||
"description": "Voir les alias des notes des clubs et des adhérent⋅es du club BDE"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -772,7 +772,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir les adhérents du club"
|
||||
"description": "Voir les adhérent⋅es du club"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -788,7 +788,7 @@
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Ajouter un membre à un club"
|
||||
"description": "Ajouter un⋅e membre à un club"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -852,7 +852,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Modifier n'importe quel utilisateur"
|
||||
"description": "Modifier n'importe quel⋅le utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -868,7 +868,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Ajouter un utilisateur"
|
||||
"description": "Ajouter un⋅e utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -1284,7 +1284,7 @@
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Inscrire un 1A au WEI"
|
||||
"description": "Inscrire un⋅e 1A au WEI"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -1956,7 +1956,7 @@
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"description": "Voir mes activitées passées, même après la fin de l'adhésion BDE"
|
||||
"description": "Voir mes activités passées, même après la fin de l'adhésion BDE"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2100,7 +2100,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel utilisateur"
|
||||
"description": "Voir n'importe quel⋅le utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2228,7 +2228,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Créer une note d'utilisateur"
|
||||
"description": "Créer une note d'utilisateur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2276,7 +2276,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir tous les adhérents de tous les clubs"
|
||||
"description": "Voir toustes les adhérent⋅es de tous les clubs"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2292,7 +2292,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Ajouter un membre à n'importe quel club"
|
||||
"description": "Ajouter un⋅e membre à n'importe quel club"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2372,7 +2372,7 @@
|
||||
"mask": 1,
|
||||
"field": "name",
|
||||
"permanent": false,
|
||||
"description": "Modifier le nom d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier le nom d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2388,7 +2388,7 @@
|
||||
"mask": 1,
|
||||
"field": "description",
|
||||
"permanent": false,
|
||||
"description": "Modifier la description d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier la description d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2404,7 +2404,7 @@
|
||||
"mask": 1,
|
||||
"field": "location",
|
||||
"permanent": false,
|
||||
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2420,7 +2420,7 @@
|
||||
"mask": 1,
|
||||
"field": "activity_type",
|
||||
"permanent": false,
|
||||
"description": "Modifier le type d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier le type d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2436,7 +2436,7 @@
|
||||
"mask": 1,
|
||||
"field": "organizer",
|
||||
"permanent": false,
|
||||
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2452,7 +2452,7 @@
|
||||
"mask": 1,
|
||||
"field": "attendees_club",
|
||||
"permanent": false,
|
||||
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2468,7 +2468,7 @@
|
||||
"mask": 1,
|
||||
"field": "date_start",
|
||||
"permanent": false,
|
||||
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2484,7 +2484,7 @@
|
||||
"mask": 1,
|
||||
"field": "date_end",
|
||||
"permanent": false,
|
||||
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur"
|
||||
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur⋅rice"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2591,12 +2591,12 @@
|
||||
"note",
|
||||
"transaction"
|
||||
],
|
||||
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]",
|
||||
"query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]",
|
||||
"type": "add",
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €"
|
||||
"description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2756,7 +2756,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Modifier n'importe quel utilisateur non encore inscrit"
|
||||
"description": "Modifier n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2788,7 +2788,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir tous les alias, y compris ceux des non adhérents"
|
||||
"description": "Voir tous les alias, y compris ceux des non adhérent⋅es"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2820,7 +2820,7 @@
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel utilisateur non encore inscrit"
|
||||
"description": "Voir n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -2847,12 +2847,12 @@
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
|
||||
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent⋅e BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
|
||||
"type": "view",
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
|
||||
"description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e BDE"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -3044,7 +3044,7 @@
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir toutes les amitiés, y compris celles des non adhérents"
|
||||
"description": "Voir toutes les amitiés, y compris celles des non adhérent⋅es"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -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": 2,
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Transférer de l'argent depuis une note amie en restant positif"
|
||||
@ -3094,13 +3094,29 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.role",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"for_club": 1,
|
||||
"name": "Adh\u00e9rent BDE",
|
||||
"name": "Adh\u00e9rent\u22c5e BDE",
|
||||
"permissions": [
|
||||
1,
|
||||
2,
|
||||
@ -3135,7 +3151,8 @@
|
||||
190,
|
||||
191,
|
||||
195,
|
||||
196
|
||||
196,
|
||||
198
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3144,7 +3161,7 @@
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"for_club": 2,
|
||||
"name": "Adh\u00e9rent Kfet",
|
||||
"name": "Adh\u00e9rent\u22c5e Kfet",
|
||||
"permissions": [
|
||||
22,
|
||||
36,
|
||||
@ -3208,10 +3225,11 @@
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"for_club": null,
|
||||
"name": "Pr\u00e9sident\u00b7e de club",
|
||||
"name": "Pr\u00e9sident\u22c5e de club",
|
||||
"permissions": [
|
||||
62,
|
||||
142
|
||||
142,
|
||||
135
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3220,7 +3238,7 @@
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"for_club": null,
|
||||
"name": "Tr\u00e9sorier\u00b7\u00e8re de club",
|
||||
"name": "Tr\u00e9sorièr\u22c5e de club",
|
||||
"permissions": [
|
||||
19,
|
||||
20,
|
||||
@ -3244,7 +3262,7 @@
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"for_club": 1,
|
||||
"name": "Pr\u00e9sident\u00b7e BDE",
|
||||
"name": "Pr\u00e9sident\u22c5e BDE",
|
||||
"permissions": [
|
||||
24,
|
||||
25,
|
||||
@ -3273,7 +3291,7 @@
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"for_club": 1,
|
||||
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE",
|
||||
"name": "Tr\u00e9sorièr\u22c5e BDE",
|
||||
"permissions": [
|
||||
23,
|
||||
24,
|
||||
@ -3441,7 +3459,7 @@
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"for_club": null,
|
||||
"name": "Chef de bus",
|
||||
"name": "Chef\u22c5fe de bus",
|
||||
"permissions": [
|
||||
22,
|
||||
84,
|
||||
@ -3460,7 +3478,7 @@
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"for_club": null,
|
||||
"name": "Chef d'\u00e9quipe",
|
||||
"name": "Chef\u22c5fe d'\u00e9quipe",
|
||||
"permissions": [
|
||||
22,
|
||||
84,
|
||||
@ -3509,7 +3527,7 @@
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"for_club": null,
|
||||
"name": "Adhérent WEI",
|
||||
"name": "Adhérent\u22c5e WEI",
|
||||
"permissions": [
|
||||
77,
|
||||
114
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import functools
|
||||
@ -26,6 +26,15 @@ 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
|
||||
@ -84,21 +93,11 @@ 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'),
|
||||
@ -110,13 +109,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):
|
||||
|
||||
@ -194,16 +193,19 @@ class Permission(models.Model):
|
||||
verbose_name = _("permission")
|
||||
verbose_name_plural = _("permissions")
|
||||
|
||||
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."))
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
@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):
|
||||
@ -317,9 +319,6 @@ 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):
|
||||
"""
|
||||
@ -344,9 +343,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
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from oauth2_provider.oauth2_validators import OAuth2Validator
|
||||
from oauth2_provider.scopes import BaseScopes
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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 BDE")
|
||||
| Q(name="Adhérent Kfet")
|
||||
roles = record.roles.filter((~(Q(name="Adhérent⋅e BDE")
|
||||
| Q(name="Adhérent⋅e Kfet")
|
||||
| Q(name="Membre de club")
|
||||
| Q(name="Bureau de club"))
|
||||
& Q(weirole__isnull=True))).all()
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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 BDE"))
|
||||
membership.roles.add(Role.objects.get(name="Adhérent⋅e 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 BDE"))
|
||||
membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
|
||||
membership.save()
|
||||
|
||||
resp = self.client.get(reverse('permission:scopes'))
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta, date
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
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 BDE")
|
||||
| Q(name="Adhérent Kfet")
|
||||
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE")
|
||||
| Q(name="Adhérent⋅e Kfet")
|
||||
| Q(name="Membre de club")
|
||||
| Q(name="Bureau de club"))
|
||||
& Q(weirole__isnull=True))))\
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'registration.apps.RegistrationConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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
|
||||
|
||||
@ -115,12 +114,3 @@ class ValidationForm(forms.Form):
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
|
||||
# If the bda exists
|
||||
if Club.objects.filter(name__iexact="bda").exists():
|
||||
# The user can join the bda club at the inscription
|
||||
join_bda = forms.BooleanField(
|
||||
label=_("Join BDA Club"),
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import django_tables2 as tables
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user