1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 01:48:21 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
51d60d064c Add waiting lists interfaces
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 23:44:49 +02:00
45334e4e02 Add order interface
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 17:27:59 +02:00
5174c84b33 Manage food options
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 14:50:45 +02:00
51e5e3669e Add interface to create and see note sheets
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-08-18 14:27:02 +02:00
44994a3ae7 Add new application to manage note sheets
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-08-18 12:33:10 +02:00
ba017c38c0 Fix permission that allows users to create OAuth2 apps
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-07-22 17:18:53 +02:00
219 changed files with 4362 additions and 3561 deletions

1
.gitignore vendored
View File

@ -42,7 +42,6 @@ map.json
backups/
/static/
/media/
/tmp/
# Virtualenv
env/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
"name": "Pot",
"manage_entries": true,
"can_invite": true,
"guest_entry_fee": 1000
"guest_entry_fee": 500
}
},
{
@ -28,25 +28,5 @@
"can_invite": false,
"guest_entry_fee": 0
}
},
{
"model": "activity.activitytype",
"pk": 5,
"fields": {
"name": "Soir\u00e9e avec entrées",
"manage_entries": true,
"can_invite": false,
"guest_entry_fee": 0
}
},
{
"model": "activity.activitytype",
"pk": 7,
"fields": {
"name": "Soir\u00e9e avec invitations",
"manage_entries": true,
"can_invite": true,
"guest_entry_fee": 0
}
}
]

View File

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

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2024-03-23 13:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('activity', '0002_auto_20200904_2341'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='description',
field=models.TextField(blank=True, default='', verbose_name='description'),
),
]

View File

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

View File

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

View File

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

View File

@ -46,4 +46,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3>
{% render_table table %}
</div>
{% endblock %}
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size_query_param = 'page_size'

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
@ -26,6 +26,10 @@ if "note" in settings.INSTALLED_APPS:
from note.api.urls import register_note_urls
register_note_urls(router, 'note')
if "sheets" in settings.INSTALLED_APPS:
from sheets.api.urls import register_sheets_urls
register_sheets_urls(router, 'sheets')
if "treasury" in settings.INSTALLED_APPS:
from treasury.api.urls import register_treasury_urls
register_treasury_urls(router, 'treasury')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import io
@ -47,13 +47,6 @@ class ProfileForm(forms.ModelForm):
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
VSS_charter_read = forms.BooleanField(
required=True,
label=_("Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"),
help_text=_("Tick after having read and accepted the anti-VSS charter \
<a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> available here in pdf</a>")
)
def clean_promotion(self):
promotion = self.cleaned_data["promotion"]
if promotion > timezone.now().year:
@ -121,7 +114,7 @@ class ImageForm(forms.Form):
frame = frame.crop((x, y, x + w, y + h))
frame = frame.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.LANCZOS,
Image.ANTIALIAS,
)
frames.append(frame)

View File

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

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.26 on 2022-09-04 21:25
# Generated by Django 2.2.27 on 2022-08-18 11:01
from django.db import migrations, models

View File

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

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2023-08-31 09:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0010_new_default_year'),
]
operations = [
migrations.AddField(
model_name='profile',
name='VSS_charter_read',
field=models.BooleanField(default=False, verbose_name='VSS charter read'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import datetime
@ -28,6 +28,7 @@ class Profile(models.Model):
We do not want to patch the Django Contrib :model:`auth.User`model;
so this model add an user profile with additional information.
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
@ -133,22 +134,6 @@ class Profile(models.Model):
default=False,
)
VSS_charter_read = models.BooleanField(
verbose_name=_("VSS charter read"),
default=False
)
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return str(self.user)
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
@property
def ens_year(self):
"""
@ -173,6 +158,17 @@ class Profile(models.Model):
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
return False
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
indexes = [models.Index(fields=['user'])]
def get_absolute_url(self):
return reverse('member:user_detail', args=(self.user_id,))
def __str__(self):
return str(self.user)
def send_email_validation_link(self):
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
token = email_validation_token.make_token(self.user)
@ -204,11 +200,9 @@ class Club(models.Model):
max_length=255,
unique=True,
)
email = models.EmailField(
verbose_name=_('email'),
)
parent_club = models.ForeignKey(
'self',
null=True,
@ -259,12 +253,25 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'),
)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def update_membership_dates(self):
"""
This function is called each time the club detail view is displayed.
Update the year of the membership dates.
"""
if not self.membership_start or not self.membership_end:
return
def __str__(self):
return self.name
today = datetime.date.today()
if (today - self.membership_start).days >= 365:
if self.membership_start:
self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day)
if self.membership_end:
self.membership_end = datetime.date(self.membership_end.year + 1,
self.membership_end.month, self.membership_end.day)
self._force_save = True
self.save(force_update=True)
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
@ -277,29 +284,16 @@ class Club(models.Model):
self.membership_end = None
super().save(force_insert, force_update, update_fields)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk,))
def update_membership_dates(self):
"""
This function is called each time the club detail view is displayed.
Update the year of the membership dates.
"""
if not self.membership_start or not self.membership_end:
return
today = datetime.date.today()
while (today - self.membership_start).days >= 365:
if self.membership_start:
self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day)
if self.membership_end:
self.membership_end = datetime.date(self.membership_end.year + 1,
self.membership_end.month, self.membership_end.day)
self._force_save = True
self.save(force_update=True)
class Membership(models.Model):
"""
@ -339,66 +333,6 @@ class Membership(models.Model):
verbose_name=_('fee'),
)
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
@property
def valid(self):
"""
@ -476,6 +410,58 @@ class Membership(models.Model):
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save()
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
"""
# Ensure that club membership dates are valid
old_membership_start = self.club.membership_start
self.club.update_membership_dates()
if self.club.membership_start != old_membership_start:
self.club.save()
created = not self.pk
if not created:
for role in self.roles.all():
club = role.for_club
if club is not None:
if club.pk != self.club_id:
raise ValidationError(_('The role {role} does not apply to the club {club}.')
.format(role=role.name, club=club.name))
else:
if Membership.objects.filter(
user=self.user,
club=self.club,
date_start__lte=self.date_start,
date_end__gte=self.date_start,
).exists():
raise ValidationError(_('User is already a member of the club'))
if self.club.parent_club is not None:
# Check that the user is already a member of the parent club if the membership is created
if not Membership.objects.filter(
user=self.user,
club=self.club.parent_club,
date_start__gte=self.club.parent_club.membership_start,
).exists():
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
self.renew_parent()
else:
raise ValidationError(_('User is not a member of the parent club')
+ ' ' + self.club.parent_club.name)
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs)
self.make_transaction()
def make_transaction(self):
"""
Create Membership transaction associated to this membership.
@ -513,3 +499,11 @@ class Membership(models.Model):
soge_credit.save()
else:
transaction.save(force_insert=True)
def __str__(self):
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
indexes = [models.Index(fields=['user'])]

View File

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

View File

@ -1,7 +1,7 @@
/**
* On form submit, create a new friendship
*/
function form_create_trust (e) {
function create_trust (e) {
// Do not submit HTML form
e.preventDefault()
@ -14,35 +14,25 @@ function form_create_trust (e) {
addMsg(gettext("You can't add yourself as a friend"), "danger")
return
}
create_trust(formData.get('trusting'), trusted_alias.note)
$.post('/api/note/trust/', {
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
trusting: formData.get('trusting'),
trusted: trusted_alias.note
}).done(function () {
// Reload table
$('#trust_table').load(location.pathname + ' #trust_table')
addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
/**
* Create a trust between users
* @param trusting:Integer trusting note id
* @param trusted:Integer trusted note id
*/
function create_trust(trusting, trusted) {
$.post('/api/note/trust/', {
trusting: trusting,
trusted: trusted,
csrfmiddlewaretoken: CSRF_TOKEN
}).done(function () {
// Reload tables
$('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
/**
* On click of "delete", delete the trust
* @param button_id:Integer Trust id to remove
* On click of "delete", delete the alias
* @param button_id:Integer Alias id to remove
*/
function delete_button (button_id) {
$.ajax({
@ -52,7 +42,6 @@ function delete_button (button_id) {
}).done(function () {
addMsg(gettext('Friendship successfully deleted'), 'success')
$('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
@ -60,5 +49,5 @@ function delete_button (button_id) {
$(document).ready(function () {
// Attach event
document.getElementById('form_trust').addEventListener('submit', form_create_trust)
document.getElementById('form_trust').addEventListener('submit', create_trust)
})

View File

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

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block profile_content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "Add friends" %}
{% trans "Note friendships" %}
</h3>
<div class="card-body">
{% if can_create %}
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% render_table trusting %}
</div>
<div class="alert alert-warning card mb-3">
<div class="alert alert-warning card">
{% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming
from your account (while keeping your balance positive). This is
@ -33,13 +33,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
friends without needing additional rights among them.
{% endblocktrans %}
</div>
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "People having you as a friend" %}
</h3>
{% render_table trusted_by %}
</div>
{% endblock %}
{% block extrajavascript %}

View File

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

View File

@ -183,7 +183,7 @@ class TestMemberships(TestCase):
club = Club.objects.get(name="Kfet")
else:
club = Club.objects.create(
name="Second club without BDE",
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
parent_club=None,
email="newclub@example.com",
require_memberships=True,
@ -335,7 +335,6 @@ class TestMemberships(TestCase):
ml_sports_registration=True,
ml_art_registration=True,
report_frequency=7,
VSS_charter_read=True
))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.assertTrue(User.objects.filter(username="toto changed").exists())

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, date
@ -8,6 +8,7 @@ from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import Q, F
from django.shortcuts import redirect
@ -20,13 +21,13 @@ from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token
from note.models import Alias, NoteClub, NoteUser, Trust
from note.models.transactions import Transaction, SpecialTransaction
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
from note.tables import HistoryTable, AliasTable, TrustTable
from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend
from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -257,18 +258,17 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
note = context['object'].note
context["trusting"] = TrustTable(
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["trusted_by"] = TrustedTable(
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
trusting=context["object"].note,
trusted=context["object"].note
))
context["widget"] = {
"name": "trusted",
"resetable": True,
"attrs": {
"model_pk": ContentType.objects.get_for_model(Alias).pk,
"class": "autocomplete form-control",
"id": "trusted",
"resetable": True,
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
"name_field": "name",
"placeholder": ""
@ -753,10 +753,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
club = old_membership.club
user = old_membership.user
# Update club membership date
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
club.update_membership_dates()
form.instance.club = club
# Get form data

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin
@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
from note_kfet.admin import admin_site
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
RecurrentTransaction, MembershipTransaction, SpecialTransaction
from .templatetags.pretty_money import pretty_money
@ -21,16 +21,6 @@ class AliasInlines(admin.TabularInline):
model = Alias
class TrustInlines(admin.TabularInline):
"""
Define trusts when editing the trusting note
"""
model = Trust
fk_name = "trusting"
extra = 0
readonly_fields = ("trusted",)
@admin.register(Note, site=admin_site)
class NoteAdmin(PolymorphicParentModelAdmin):
"""
@ -102,7 +92,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
"""
Child for an user note, see NoteAdmin
"""
inlines = (AliasInlines, TrustInlines)
inlines = (AliasInlines,)
# We can't change user after creation or the balance
readonly_fields = ('user', 'balance')

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
@ -11,7 +11,6 @@ from member.models import Membership
from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend
from rest_framework.utils import model_meta
from rest_framework.validators import UniqueTogetherValidator
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
@ -87,9 +86,11 @@ class TrustSerializer(serializers.ModelSerializer):
class Meta:
model = Trust
fields = '__all__'
validators = [UniqueTogetherValidator(
queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
message=_("This friendship already exists"))]
def validate(self, attrs):
instance = Trust(**attrs)
instance.clean()
return attrs
class AliasSerializer(serializers.ModelSerializer):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
// Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
// SPDX-License-Identifier: GPL-3.0-or-later
// When a transaction is performed, lock the interface to prevent spam clicks.
@ -221,7 +221,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
.done(function () {
if (!isNaN(source.balance)) {
const newBalance = source.balance - quantity * amount
if (newBalance <= -2000) {
if (newBalance <= -5000) {
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000)
} else if (newBalance < 0) {
@ -258,39 +258,3 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
})
})
}
var searchbar = document.getElementById("search-input")
var search_results = document.getElementById("search-results")
var old_pattern = null;
var firstMatch = null;
/**
* Updates the button search tab
* @param force Forces the update even if the pattern didn't change
*/
function updateSearch(force = false) {
let pattern = searchbar.value
if (pattern === "")
firstMatch = null;
if ((pattern === old_pattern || pattern === "") && !force)
return;
firstMatch = null;
const re = new RegExp(pattern, "i");
Array.from(search_results.children).forEach(function(b) {
if (re.test(b.innerText)) {
b.hidden = false;
if (firstMatch === null) {
firstMatch = b;
}
} else
b.hidden = true;
});
}
searchbar.addEventListener("input", function (e) {
debounce(updateSearch)()
});
searchbar.addEventListener("keyup", function (e) {
if (firstMatch && e.key === "Enter")
firstMatch.click()
});

View File

@ -314,7 +314,7 @@ $('#btn_transfer').click(function () {
if (!isNaN(source.note.balance)) {
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
if (newBalance <= -2000) {
if (newBalance <= -5000) {
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'),
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
reset()

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import html
@ -159,11 +159,11 @@ class TrustTable(tables.Table):
template_name = 'django_tables2/bootstrap4.html'
show_header = False
trusted = tables.Column(attrs={'td': {'class': 'text-center'}})
trusted = tables.Column(attrs={'td': {'class': 'text_center'}})
delete_col = tables.TemplateColumn(
template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('Delete')},
extra_context={"delete_trans": _('delete')},
attrs={
'td': {
'class': lambda record: 'col-sm-1'
@ -173,46 +173,6 @@ class TrustTable(tables.Table):
verbose_name=_("Delete"),)
class TrustedTable(tables.Table):
class Meta:
attrs = {
'class': 'table table condensed table-striped',
'id': 'trusted_table'
}
Model = Trust
fields = ("trusting",)
template_name = "django_tables2/bootstrap4.html"
show_header = False
trusting = tables.Column(attrs={
'td': {'class': 'text-center', 'width': '100%'}})
trust_back = tables.Column(
verbose_name=_("Trust back"),
accessor="pk",
attrs={
'td': {
'class': '',
'id': lambda record: "trust_back_" + str(record.pk),
}
},
)
def render_trust_back(self, record):
user_note = record.trusted
trusting_note = record.trusting
if Trust.objects.filter(trusted=trusting_note, trusting=user_note):
return ""
val = '<button id="'
val += str(record.pk)
val += '" class="btn btn-success btn-sm text-nowrap" \
onclick="create_trust(' + str(record.trusted.pk) + ',' + \
str(record.trusting.pk) + ')">'
val += str(_("Add back"))
val += '</button>'
return mark_safe(val)
class AliasTable(tables.Table):
class Meta:
attrs = {

View File

@ -103,11 +103,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a>
</li>
{% endfor %}
<li class="nav-item">
<a class="nav-link font-weight-bold" data-toggle="tab" href="#search">
{% trans "Search" %}
</a>
</li>
</ul>
</div>
@ -128,20 +123,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div>
</div>
{% endfor %}
<div class="tab-pane" id="search">
<input class="form-control mx-auto d-block mb-3"
placeholder="{% trans "Search button..." %}" type="search" id="search-input"/>
<div class="d-inline-flex flex-wrap justify-content-center" id="search-results">
{% for button in all_buttons %}
{% if button.display %}
<button class="btn btn-outline-dark rounded-0 flex-fill" hidden
id="search_button{{ button.id }}" name="button" value="{{ button.name }}">
{{ button.name }} ({{ button.amount | pretty_money }})
</button>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
@ -182,7 +163,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<script type="text/javascript">
{% for button in highlighted %}
{% if button.display %}
document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() {
$("#highlighted_button{{ button.id }}").click(function() {
addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}");
@ -193,7 +174,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% for category in categories %}
{% for button in category.templates_filtered %}
{% if button.display %}
document.getElementById("button{{ button.id }}").addEventListener("click", function() {
$("#button{{ button.id }}").click(function() {
addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}");
@ -201,15 +182,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %}
{% endfor %}
{% endfor %}
{% for button in all_buttons %}
{% if button.display %}
document.getElementById("search_button{{ button.id }}").addEventListener("click", function() {
addConso({{ button.destination_id }}, {{ button.amount }},
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
{{ button.id }}, "{{ button.name|escapejs }}");
});
{% endif %}
{% endfor %}
</script>
{% endblock %}

View File

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

View File

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
{% crispy form %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,88 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header text-center">
<h1>{{ food.name }}</h1>
</div>
<div class="card-body">
<div class="row">
<div class="card col-xl-6">
<div class="card-header text-center">
<h2>{% trans "queued"|capfirst %}{% if queue %} ({{ queue|length }}){% endif %}</h2>
</div>
<div class="card-body">
<ul>
{% for ordered_food in queue %}
<li>
{{ ordered_food.order.note }}
{% if ordered_food.priority %}
<span class="badge badge-secondary">{{ ordered_food.priority }}</span>
{% endif %}
</li>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no queued order." %}
</div>
{% endfor %}
</ul>
</div>
</div>
<div class="card col-xl-6">
<div class="card-header text-center">
<h2>{% trans "ready"|capfirst %}</h2>
</div>
<div class="card-body">
<ul>
{% for ordered_food in ready %}
<li>{{ ordered_food.order.note }}</li>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no ready order." %}
</div>
{% endfor %}
</ul>
</div>
</div>
</div>
<hr>
<h3>{% trans "Other waiting lists:" %}</h3>
<ul>
{% for other_food in food.sheet.food_set.all %}
{% if other_food != food %}
<li>
<a href="{% url 'sheets:waiting_list' pk=other_food.pk %}">{{ other_food }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="card-footer text-center">
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Queued orders" %}
</a>
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Ready orders" %}
</a>
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
{% trans "Back to note sheet detail" %}
</a>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function reload() {
reloadWithTurbolinks()
timeout = setTimeout(reload, 15000)
}
if (timeout === undefined)
var timeout = setTimeout(reload, 15000)
</script>
{% endblock %}

View File

@ -0,0 +1,152 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header text-center">
<h1>{{ title }}</h1>
</div>
<div class="card-body">
{% for of in orders %}
<div class="card mb-4">
<div class="card-header text-center">
<h3>{{ of.order.note }}</h3>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-xl-3">{% trans 'date'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.order.date }}</dd>
{% if of.number > 1 %}
<dt class="col-xl-3">{% trans 'order number'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.number }}</dd>
{% endif %}
{% if of.priority %}
<dt class="col-xl-3">{% trans 'priority request'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.priority }}</dd>
{% endif %}
{% if of.remark %}
<dt class="col-xl-3">{% trans 'remark'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.remark }}</dd>
{% endif %}
{% if of.options.count %}
<dt class="col-xl-3">{% trans 'options'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.options.all|join:', ' }}</dd>
{% endif %}
</dl>
</div>
<div class="card-footer text-center">
{% if list_type != 'READY' %}
<a href="#" class="btn btn-success" onclick="setOrderStatus({{ of.pk }}, 'READY')">
{% trans "Mark as ready" %}
</a>
{% endif %}
{% if list_type != 'SERVED' %}
<a href="#" class="btn btn-primary" onclick="setOrderStatus({{ of.pk }}, 'SERVED')">
{% trans "Mark as served" %}
</a>
{% endif %}
{% if list_type != 'QUEUED' %}
<a href="#" class="btn btn-warning" onclick="setOrderStatus({{ of.pk }}, 'QUEUED')">
{% trans "Re-queue" %}
</a>
{% endif %}
{% if list_type != 'CANCELED' %}
<a href="#" class="btn btn-danger" onclick="setOrderStatus({{ of.pk }}, 'CANCELED')">
{% trans "Cancel" %}
</a>
{% endif %}
</div>
</div>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no queued order." %}
</div>
{% endfor %}
</div>
</div>
<div class="card mt-5">
<div class="card-body">
<h3>{% trans "Other waiting lists:" %}</h3>
<ul>
{% for other_food in food.sheet.food_set.all %}
{% if other_food != food %}
<li>
{% if list_type == 'QUEUED' %}
<a href="{% url 'sheets:queued_list' pk=other_food.pk %}">{{ other_food }}</a>
{% else %}
<a href="{% url 'sheets:ready_list' pk=other_food.pk %}">{{ other_food }}</a>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="card-footer text-center">
{% if list_type != 'QUEUED' %}
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Queued orders" %}
</a>
{% endif %}
{% if list_type != 'READY' %}
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-success">
{% trans "Ready orders" %}
</a>
{% endif %}
{% if list_type != 'SERVED' %}
<a href="{% url 'sheets:served_list' pk=food.pk %}" class="btn btn-secondary">
{% trans "Served orders" %}
</a>
{% endif %}
{% if list_type != 'CANCELED' %}
<a href="{% url 'sheets:canceled_list' pk=food.pk %}" class="btn btn-danger">
{% trans "Canceled orders" %}
</a>
{% endif %}
<a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Waiting list" %}
</a>
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
{% trans "Back to note sheet detail" %}
</a>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function reload() {
reloadWithTurbolinks()
timeout = setTimeout(reload, 15000)
}
if (timeout === undefined)
var timeout = setTimeout(reload, 15000)
function setOrderStatus(ordered_food_id, status) {
fetch('/api/sheets/orderedfood/' + ordered_food_id + '/', {
method: 'PATCH',
body: JSON.stringify({
status: status,
served_date: status === 'QUEUED' ? null : new Date().toISOString(),
}),
headers: {
'Content-Type': "application/json; charset=UTF-8",
'X-CSRFTOKEN': "{{ csrf_token }}"
}
}).then(response => response.json()).then(response => {
if ('detail' in response)
addMsg("{% trans "An error occurred" %}" + " : " + response['detail'], "danger")
else {
clearTimeout(timeout)
reload()
}
})
}
</script>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import json
@ -10,12 +10,12 @@ from django.core.exceptions import PermissionDenied
from django.db.models import Q, F
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, UpdateView, DetailView
from django.urls import reverse_lazy
from django_tables2 import SingleTableView
from django.urls import reverse_lazy
from activity.models import Entry
from note_kfet.inputs import AmountInput
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from note_kfet.inputs import AmountInput
from .forms import TransactionTemplateForm, SearchTransactionForm
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
@ -190,10 +190,6 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
).order_by('name').all()
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
context['all_buttons'] = TransactionTemplate.objects.filter(
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
).filter(display=True).order_by('name').all()
return context

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
@ -198,41 +198,6 @@ class PermissionBackend(ModelBackend):
def has_module_perms(self, user_obj, app_label):
return False
@staticmethod
@memoize
def has_model_perm(request, model, type):
"""
Check is the given user has the permission over a given model for a given action.
The result is then memoized.
:param request: The current request
:param model: The model that the permissions shoud apply
:param type: The type of the permissions: view, change, add or delete
For view action, it is consider possible if user can view or change the model
"""
# Requested by a shell
if request is None:
return False
user_obj = request.user
sess = request.session
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
# OAuth2 Authentication
user_obj = request.auth.user
if user_obj is None or user_obj.is_anonymous:
return False
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
return True
ct = ContentType.objects.get_for_model(model)
if any(PermissionBackend.permissions(request, ct, type)):
return True
if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
return True
return False
def get_all_permissions(self, user_obj, obj=None):
ct = ContentType.objects.get_for_model(obj)
return list(self.permissions(get_current_request(), ct, "view"))

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
# Generated by Django 2.2.28 on 2023-07-24 10:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('permission', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='role',
name='for_club',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='for club'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import functools
@ -26,15 +26,6 @@ class InstancedPermission:
self.mask = mask
self.kwargs = kwargs
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else:
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
def applies(self, obj, permission_type, field_name=None):
"""
Returns True if the permission applies to
@ -93,11 +84,21 @@ class InstancedPermission:
# noinspection PyProtectedMember
self.query = Permission._about(self.raw_query, **self.kwargs)
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else:
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
class PermissionMask(models.Model):
"""
Permissions that are hidden behind a mask
"""
rank = models.PositiveSmallIntegerField(
unique=True,
verbose_name=_('rank'),
@ -109,13 +110,13 @@ class PermissionMask(models.Model):
verbose_name=_('description'),
)
def __str__(self):
return self.description
class Meta:
verbose_name = _("permission mask")
verbose_name_plural = _("permission masks")
def __str__(self):
return self.description
class Permission(models.Model):
@ -193,19 +194,16 @@ class Permission(models.Model):
verbose_name = _("permission")
verbose_name_plural = _("permissions")
def __str__(self):
return self.description
def clean(self):
self.query = json.dumps(json.loads(self.query))
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@transaction.atomic
def save(self, **kwargs):
self.full_clean()
super().save()
def clean(self):
self.query = json.dumps(json.loads(self.query))
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@staticmethod
def compute_f(oper, **kwargs):
if isinstance(oper, list):
@ -319,6 +317,9 @@ class Permission(models.Model):
# query = self._about(query, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
def __str__(self):
return self.description
class Role(models.Model):
"""
@ -338,14 +339,13 @@ class Role(models.Model):
"member.Club",
verbose_name=_("for club"),
on_delete=models.PROTECT,
blank=True,
null=True,
default=None,
)
def __str__(self):
return self.name
class Meta:
verbose_name = _("role permissions")
verbose_name_plural = _("role permissions")
def __str__(self):
return self.name

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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