mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-27 20:22:15 +02:00
Compare commits
70 Commits
3ff30bf0e7
...
migration-
Author | SHA1 | Date | |
---|---|---|---|
bb9f58e497 | |||
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 |
@ -7,25 +7,10 @@ stages:
|
|||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
|
|
||||||
# Debian Buster
|
# Ubuntu 22.04
|
||||||
py37-django22:
|
py310-django50:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:buster-backports
|
image: ubuntu:22.04
|
||||||
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:
|
|
||||||
stage: test
|
|
||||||
image: ubuntu:20.04
|
|
||||||
before_script:
|
before_script:
|
||||||
# Fix tzdata prompt
|
# Fix tzdata prompt
|
||||||
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||||
@ -37,12 +22,12 @@ py38-django22:
|
|||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
python3-bs4 python3-setuptools tox texlive-xetex
|
||||||
script: tox -e py38-django22
|
script: tox -e py310-django50
|
||||||
|
|
||||||
# Debian Bullseye
|
# Debian Bookworm
|
||||||
py39-django22:
|
py311-django50:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:bullseye
|
image: debian:bookworm
|
||||||
before_script:
|
before_script:
|
||||||
- >
|
- >
|
||||||
apt-get update &&
|
apt-get update &&
|
||||||
@ -52,11 +37,11 @@ py39-django22:
|
|||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
python3-bs4 python3-setuptools tox texlive-xetex
|
||||||
script: tox -e py39-django22
|
script: tox -e py311-django50
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: debian:buster-backports
|
image: debian:bullseye
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update && apt-get install -y tox
|
- apt-get update && apt-get install -y tox
|
||||||
script: tox -e linters
|
script: tox -e linters
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'activity.apps.ActivityConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.viewsets import ReadProtectedModelViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
|
||||||
@ -10,7 +12,7 @@ from django.utils import timezone
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from member.models import Club
|
from member.models import Club
|
||||||
from note.models import Note, NoteUser
|
from note.models import Note, NoteUser
|
||||||
from note_kfet.inputs import Autocomplete, DateTimePickerInput
|
from note_kfet.inputs import Autocomplete
|
||||||
from note_kfet.middlewares import get_current_request
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -66,6 +66,8 @@ class Activity(models.Model):
|
|||||||
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
|
blank=True,
|
||||||
|
default="",
|
||||||
)
|
)
|
||||||
|
|
||||||
location = models.CharField(
|
location = models.CharField(
|
||||||
@ -123,6 +125,14 @@ class Activity(models.Model):
|
|||||||
verbose_name=_('open'),
|
verbose_name=_('open'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("activity")
|
||||||
|
verbose_name_plural = _("activities")
|
||||||
|
unique_together = ("name", "date_start", "date_end",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -144,14 +154,6 @@ class Activity(models.Model):
|
|||||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("activity")
|
|
||||||
verbose_name_plural = _("activities")
|
|
||||||
unique_together = ("name", "date_start", "date_end",)
|
|
||||||
|
|
||||||
|
|
||||||
class Entry(models.Model):
|
class Entry(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -252,14 +254,13 @@ class Guest(models.Model):
|
|||||||
verbose_name=_("inviter"),
|
verbose_name=_("inviter"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
class Meta:
|
||||||
def has_entry(self):
|
verbose_name = _("guest")
|
||||||
try:
|
verbose_name_plural = _("guests")
|
||||||
if self.entry:
|
unique_together = ("activity", "last_name", "first_name", )
|
||||||
return True
|
|
||||||
return False
|
def __str__(self):
|
||||||
except AttributeError:
|
return self.first_name + " " + self.last_name
|
||||||
return False
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
@ -290,13 +291,14 @@ class Guest(models.Model):
|
|||||||
|
|
||||||
return super().save(force_insert, force_update, using, update_fields)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
def __str__(self):
|
@property
|
||||||
return self.first_name + " " + self.last_name
|
def has_entry(self):
|
||||||
|
try:
|
||||||
class Meta:
|
if self.entry:
|
||||||
verbose_name = _("guest")
|
return True
|
||||||
verbose_name_plural = _("guests")
|
return False
|
||||||
unique_together = ("activity", "last_name", "first_name", )
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class GuestTransaction(Transaction):
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -17,4 +17,27 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
var date_end = document.getElementById("id_date_end");
|
||||||
|
var date_start = document.getElementById("id_date_start");
|
||||||
|
|
||||||
|
function update_date_end (){
|
||||||
|
if(date_end.value=="" || date_end.value<date_start.value){
|
||||||
|
date_end.value = date_start.value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function update_date_start (){
|
||||||
|
if(date_start.value=="" || date_end.value<date_start.value){
|
||||||
|
date_start.value = date_end.value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
date_start.addEventListener('focusout', update_date_end);
|
||||||
|
date_end.addEventListener('focusout', update_date_start);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
@ -76,6 +76,7 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
|
|||||||
context['upcoming'] = ActivityTable(
|
context['upcoming'] = ActivityTable(
|
||||||
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
|
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
|
||||||
prefix='upcoming-',
|
prefix='upcoming-',
|
||||||
|
order_by='date_start',
|
||||||
)
|
)
|
||||||
|
|
||||||
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'api.apps.APIConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
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
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url, include
|
from django.urls import include, re_path
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from .views import UserInformationView
|
from .views import UserInformationView
|
||||||
@ -47,7 +47,7 @@ app_name = 'api'
|
|||||||
# Wire up our API using automatic URL routing.
|
# Wire up our API using automatic URL routing.
|
||||||
# Additionally, we include login URLs for the browsable API.
|
# Additionally, we include login URLs for the browsable API.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^', include(router.urls)),
|
re_path('^', include(router.urls)),
|
||||||
url('^me/', UserInformationView.as_view()),
|
re_path('^me/', UserInformationView.as_view()),
|
||||||
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
re_path('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
]
|
]
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'logs.apps.LogsConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ChangelogViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -76,9 +76,6 @@ class Changelog(models.Model):
|
|||||||
verbose_name=_('timestamp'),
|
verbose_name=_('timestamp'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
|
||||||
raise ValidationError(_("Logs cannot be destroyed."))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("changelog")
|
verbose_name = _("changelog")
|
||||||
verbose_name_plural = _("changelogs")
|
verbose_name_plural = _("changelogs")
|
||||||
@ -86,3 +83,6 @@ class Changelog(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
||||||
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
raise ValidationError(_("Logs cannot be destroyed."))
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'member.apps.MemberConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
from bootstrap_datepicker_plus.widgets import DatePickerInput
|
||||||
from PIL import Image, ImageSequence
|
from PIL import Image, ImageSequence
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -13,7 +14,7 @@ from django.forms import CheckboxSelectMultiple
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note.models import NoteSpecial, Alias
|
from note.models import NoteSpecial, Alias
|
||||||
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
|
from note_kfet.inputs import Autocomplete, AmountInput
|
||||||
from permission.models import PermissionMask, Role
|
from permission.models import PermissionMask, Role
|
||||||
|
|
||||||
from .models import Profile, Club, Membership
|
from .models import Profile, Club, Membership
|
||||||
@ -32,7 +33,7 @@ class UserForm(forms.ModelForm):
|
|||||||
# Django usernames can only contain letters, numbers, @, ., +, - and _.
|
# Django usernames can only contain letters, numbers, @, ., +, - and _.
|
||||||
# We want to allow users to have uncommon and unpractical usernames:
|
# We want to allow users to have uncommon and unpractical usernames:
|
||||||
# That is their problem, and we have normalized aliases for us.
|
# That is their problem, and we have normalized aliases for us.
|
||||||
return super()._get_validation_exclusions() + ["username"]
|
return super()._get_validation_exclusions() | {"username"}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@ -121,7 +122,7 @@ class ImageForm(forms.Form):
|
|||||||
frame = frame.crop((x, y, x + w, y + h))
|
frame = frame.crop((x, y, x + w, y + h))
|
||||||
frame = frame.resize(
|
frame = frame.resize(
|
||||||
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
|
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
|
||||||
Image.ANTIALIAS,
|
Image.LANCZOS,
|
||||||
)
|
)
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
|
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@ -28,7 +28,6 @@ class Profile(models.Model):
|
|||||||
We do not want to patch the Django Contrib :model:`auth.User`model;
|
We do not want to patch the Django Contrib :model:`auth.User`model;
|
||||||
so this model add an user profile with additional information.
|
so this model add an user profile with additional information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -139,6 +138,17 @@ class Profile(models.Model):
|
|||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('user profile')
|
||||||
|
verbose_name_plural = _('user profile')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.user)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('member:user_detail', args=(self.user_id,))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ens_year(self):
|
def ens_year(self):
|
||||||
"""
|
"""
|
||||||
@ -163,17 +173,6 @@ class Profile(models.Model):
|
|||||||
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('user profile')
|
|
||||||
verbose_name_plural = _('user profile')
|
|
||||||
indexes = [models.Index(fields=['user'])]
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('member:user_detail', args=(self.user_id,))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
def send_email_validation_link(self):
|
def send_email_validation_link(self):
|
||||||
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
||||||
token = email_validation_token.make_token(self.user)
|
token = email_validation_token.make_token(self.user)
|
||||||
@ -205,9 +204,11 @@ class Club(models.Model):
|
|||||||
max_length=255,
|
max_length=255,
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
verbose_name=_('email'),
|
verbose_name=_('email'),
|
||||||
)
|
)
|
||||||
|
|
||||||
parent_club = models.ForeignKey(
|
parent_club = models.ForeignKey(
|
||||||
'self',
|
'self',
|
||||||
null=True,
|
null=True,
|
||||||
@ -258,6 +259,27 @@ class Club(models.Model):
|
|||||||
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("club")
|
||||||
|
verbose_name_plural = _("clubs")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if not self.require_memberships:
|
||||||
|
self.membership_fee_paid = 0
|
||||||
|
self.membership_fee_unpaid = 0
|
||||||
|
self.membership_duration = None
|
||||||
|
self.membership_start = None
|
||||||
|
self.membership_end = None
|
||||||
|
super().save(force_insert, force_update, update_fields)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||||
|
|
||||||
def update_membership_dates(self):
|
def update_membership_dates(self):
|
||||||
"""
|
"""
|
||||||
This function is called each time the club detail view is displayed.
|
This function is called each time the club detail view is displayed.
|
||||||
@ -278,27 +300,6 @@ class Club(models.Model):
|
|||||||
self._force_save = True
|
self._force_save = True
|
||||||
self.save(force_update=True)
|
self.save(force_update=True)
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None,
|
|
||||||
update_fields=None):
|
|
||||||
if not self.require_memberships:
|
|
||||||
self.membership_fee_paid = 0
|
|
||||||
self.membership_fee_unpaid = 0
|
|
||||||
self.membership_duration = None
|
|
||||||
self.membership_start = None
|
|
||||||
self.membership_end = None
|
|
||||||
super().save(force_insert, force_update, update_fields)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("club")
|
|
||||||
verbose_name_plural = _("clubs")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -338,6 +339,66 @@ class Membership(models.Model):
|
|||||||
verbose_name=_('fee'),
|
verbose_name=_('fee'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('membership')
|
||||||
|
verbose_name_plural = _('memberships')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||||
|
"""
|
||||||
|
# Ensure that club membership dates are valid
|
||||||
|
old_membership_start = self.club.membership_start
|
||||||
|
self.club.update_membership_dates()
|
||||||
|
if self.club.membership_start != old_membership_start:
|
||||||
|
self.club.save()
|
||||||
|
|
||||||
|
created = not self.pk
|
||||||
|
if not created:
|
||||||
|
for role in self.roles.all():
|
||||||
|
club = role.for_club
|
||||||
|
if club is not None:
|
||||||
|
if club.pk != self.club_id:
|
||||||
|
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||||
|
.format(role=role.name, club=club.name))
|
||||||
|
else:
|
||||||
|
if Membership.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
club=self.club,
|
||||||
|
date_start__lte=self.date_start,
|
||||||
|
date_end__gte=self.date_start,
|
||||||
|
).exists():
|
||||||
|
raise ValidationError(_('User is already a member of the club'))
|
||||||
|
|
||||||
|
if self.club.parent_club is not None:
|
||||||
|
# Check that the user is already a member of the parent club if the membership is created
|
||||||
|
if not Membership.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
club=self.club.parent_club,
|
||||||
|
date_start__gte=self.club.parent_club.membership_start,
|
||||||
|
).exists():
|
||||||
|
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||||
|
self.renew_parent()
|
||||||
|
else:
|
||||||
|
raise ValidationError(_('User is not a member of the parent club')
|
||||||
|
+ ' ' + self.club.parent_club.name)
|
||||||
|
|
||||||
|
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||||
|
|
||||||
|
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||||
|
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||||
|
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||||
|
self.date_end = self.club.membership_end
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
self.make_transaction()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
"""
|
"""
|
||||||
@ -415,58 +476,6 @@ class Membership(models.Model):
|
|||||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||||
parent_membership.save()
|
parent_membership.save()
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
|
||||||
"""
|
|
||||||
# Ensure that club membership dates are valid
|
|
||||||
old_membership_start = self.club.membership_start
|
|
||||||
self.club.update_membership_dates()
|
|
||||||
if self.club.membership_start != old_membership_start:
|
|
||||||
self.club.save()
|
|
||||||
|
|
||||||
created = not self.pk
|
|
||||||
if not created:
|
|
||||||
for role in self.roles.all():
|
|
||||||
club = role.for_club
|
|
||||||
if club is not None:
|
|
||||||
if club.pk != self.club_id:
|
|
||||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
|
||||||
.format(role=role.name, club=club.name))
|
|
||||||
else:
|
|
||||||
if Membership.objects.filter(
|
|
||||||
user=self.user,
|
|
||||||
club=self.club,
|
|
||||||
date_start__lte=self.date_start,
|
|
||||||
date_end__gte=self.date_start,
|
|
||||||
).exists():
|
|
||||||
raise ValidationError(_('User is already a member of the club'))
|
|
||||||
|
|
||||||
if self.club.parent_club is not None:
|
|
||||||
# Check that the user is already a member of the parent club if the membership is created
|
|
||||||
if not Membership.objects.filter(
|
|
||||||
user=self.user,
|
|
||||||
club=self.club.parent_club,
|
|
||||||
date_start__gte=self.club.parent_club.membership_start,
|
|
||||||
).exists():
|
|
||||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
|
||||||
self.renew_parent()
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('User is not a member of the parent club')
|
|
||||||
+ ' ' + self.club.parent_club.name)
|
|
||||||
|
|
||||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
|
||||||
|
|
||||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
|
||||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
|
||||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
|
||||||
self.date_end = self.club.membership_end
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
self.make_transaction()
|
|
||||||
|
|
||||||
def make_transaction(self):
|
def make_transaction(self):
|
||||||
"""
|
"""
|
||||||
Create Membership transaction associated to this membership.
|
Create Membership transaction associated to this membership.
|
||||||
@ -504,11 +513,3 @@ class Membership(models.Model):
|
|||||||
soge_credit.save()
|
soge_credit.save()
|
||||||
else:
|
else:
|
||||||
transaction.save(force_insert=True)
|
transaction.save(force_insert=True)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('membership')
|
|
||||||
verbose_name_plural = _('memberships')
|
|
||||||
indexes = [models.Index(fields=['user'])]
|
|
||||||
|
@ -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
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
|
|||||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
|
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
|
||||||
|
|
||||||
def test_logout(self):
|
def test_logout(self):
|
||||||
response = self.client.get(reverse("logout"))
|
response = self.client.post(reverse("logout"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_admin_index(self):
|
def test_admin_index(self):
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
@ -26,7 +26,7 @@ from permission.backends import PermissionBackend
|
|||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||||
|
|
||||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
|
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
|
||||||
CustomAuthenticationForm, MembershipRolesForm
|
CustomAuthenticationForm, MembershipRolesForm
|
||||||
from .models import Club, Membership
|
from .models import Club, Membership
|
||||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'note.apps.NoteConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
||||||
@ -13,7 +13,7 @@ def register_note_urls(router, path):
|
|||||||
router.register(path + '/note', NotePolymorphicViewSet)
|
router.register(path + '/note', NotePolymorphicViewSet)
|
||||||
router.register(path + '/alias', AliasViewSet)
|
router.register(path + '/alias', AliasViewSet)
|
||||||
router.register(path + '/trust', TrustViewSet)
|
router.register(path + '/trust', TrustViewSet)
|
||||||
router.register(path + '/consumer', ConsumerViewSet)
|
router.register(path + '/consumer', ConsumerViewSet, basename="consumer")
|
||||||
|
|
||||||
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
||||||
router.register(path + '/transaction/transaction', TransactionViewSet)
|
router.register(path + '/transaction/transaction', TransactionViewSet)
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ from rest_framework import status
|
|||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
|
||||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
|
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
|
||||||
TrustSerializer
|
TrustSerializer
|
||||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
|
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
|
||||||
@ -179,19 +179,10 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
# We match first an alias if it is matched without normalization,
|
# We match first an alias if it is matched without normalization,
|
||||||
# then if the normalized pattern matches a normalized alias.
|
# then if the normalized pattern matches a normalized alias.
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
**{f'name{suffix}': alias_prefix + alias}
|
Q(**{f'name{suffix}': alias_prefix + alias})
|
||||||
).union(
|
| Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
||||||
queryset.filter(
|
| Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
|
||||||
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
)
|
||||||
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
|
||||||
),
|
|
||||||
all=True).union(
|
|
||||||
queryset.filter(
|
|
||||||
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
|
|
||||||
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
|
||||||
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
|
||||||
),
|
|
||||||
all=True)
|
|
||||||
|
|
||||||
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
|
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
|
||||||
else queryset.order_by("name")
|
else queryset.order_by("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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.forms import CheckboxSelectMultiple
|
from django.forms import CheckboxSelectMultiple
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
|
from note_kfet.inputs import Autocomplete, AmountInput
|
||||||
|
|
||||||
from .models import TransactionTemplate, NoteClub, Alias
|
from .models import TransactionTemplate, NoteClub, Alias
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ def create_special_notes(apps, schema_editor):
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('note', '0001_initial'),
|
('note', '0001_initial'),
|
||||||
|
('logs', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.0.7 on 2024-07-11 09:24
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('note', '0006_trust'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='note',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
@ -293,6 +293,11 @@ class Alias(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(string):
|
def normalize(string):
|
||||||
"""
|
"""
|
||||||
@ -321,11 +326,6 @@ class Alias(models.Model):
|
|||||||
pass
|
pass
|
||||||
self.normalized_name = normalized_name
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
raise ValidationError(_("You can't delete your main alias."),
|
raise ValidationError(_("You can't delete your main alias."),
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
|
|||||||
amount = models.PositiveIntegerField(
|
amount = models.PositiveIntegerField(
|
||||||
verbose_name=_('amount'),
|
verbose_name=_('amount'),
|
||||||
)
|
)
|
||||||
|
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
TemplateCategory,
|
TemplateCategory,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
|
|||||||
verbose_name = _("transaction template")
|
verbose_name = _("transaction template")
|
||||||
verbose_name_plural = _("transaction templates")
|
verbose_name_plural = _("transaction templates")
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('note:template_update', args=(self.pk,))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('note:template_update', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class Transaction(PolymorphicModel):
|
class Transaction(PolymorphicModel):
|
||||||
"""
|
"""
|
||||||
@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
|
|||||||
amount is store in centimes of currency, making it a positive integer
|
amount is store in centimes of currency, making it a positive integer
|
||||||
value. (from someone to someone else)
|
value. (from someone to someone else)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source = models.ForeignKey(
|
source = models.ForeignKey(
|
||||||
Note,
|
Note,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
|
|||||||
models.Index(fields=['destination']),
|
models.Index(fields=['destination']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
|
||||||
|
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
When saving, also transfer money between two notes
|
||||||
|
"""
|
||||||
|
if self.source.pk == self.destination.pk:
|
||||||
|
# When source == destination, no money is transferred and no transaction is created
|
||||||
|
return
|
||||||
|
|
||||||
|
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||||
|
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||||
|
|
||||||
|
# Check that the amounts stay between big integer bounds
|
||||||
|
diff_source, diff_dest = self.validate()
|
||||||
|
|
||||||
|
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||||
|
and (not self.source.is_active or not self.destination.is_active):
|
||||||
|
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||||
|
"or the destination note is not active."))
|
||||||
|
|
||||||
|
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||||
|
if not self.source_alias:
|
||||||
|
self.source_alias = str(self.source)
|
||||||
|
|
||||||
|
if not self.destination_alias:
|
||||||
|
self.destination_alias = str(self.destination)
|
||||||
|
|
||||||
|
# We save first the transaction, in case of the user has no right to transfer money
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Save notes
|
||||||
|
self.source.refresh_from_db()
|
||||||
|
self.source.balance += diff_source
|
||||||
|
self.source._force_save = True
|
||||||
|
self.source.save()
|
||||||
|
self.destination.refresh_from_db()
|
||||||
|
self.destination.balance += diff_dest
|
||||||
|
self.destination._force_save = True
|
||||||
|
self.destination.save()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
previous_source_balance = self.source.balance
|
previous_source_balance = self.source.balance
|
||||||
previous_dest_balance = self.destination.balance
|
previous_dest_balance = self.destination.balance
|
||||||
@ -208,46 +252,6 @@ class Transaction(PolymorphicModel):
|
|||||||
|
|
||||||
return source_balance - previous_source_balance, dest_balance - previous_dest_balance
|
return source_balance - previous_source_balance, dest_balance - previous_dest_balance
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
When saving, also transfer money between two notes
|
|
||||||
"""
|
|
||||||
if self.source.pk == self.destination.pk:
|
|
||||||
# When source == destination, no money is transferred and no transaction is created
|
|
||||||
return
|
|
||||||
|
|
||||||
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
|
||||||
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
|
||||||
|
|
||||||
# Check that the amounts stay between big integer bounds
|
|
||||||
diff_source, diff_dest = self.validate()
|
|
||||||
|
|
||||||
if not (hasattr(self, '_force_save') and self._force_save) \
|
|
||||||
and (not self.source.is_active or not self.destination.is_active):
|
|
||||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
|
||||||
"or the destination note is not active."))
|
|
||||||
|
|
||||||
# If the aliases are not entered, we assume that the used alias is the name of the note
|
|
||||||
if not self.source_alias:
|
|
||||||
self.source_alias = str(self.source)
|
|
||||||
|
|
||||||
if not self.destination_alias:
|
|
||||||
self.destination_alias = str(self.destination)
|
|
||||||
|
|
||||||
# We save first the transaction, in case of the user has no right to transfer money
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
# Save notes
|
|
||||||
self.source.refresh_from_db()
|
|
||||||
self.source.balance += diff_source
|
|
||||||
self.source._force_save = True
|
|
||||||
self.source.save()
|
|
||||||
self.destination.refresh_from_db()
|
|
||||||
self.destination.balance += diff_dest
|
|
||||||
self.destination._force_save = True
|
|
||||||
self.destination.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.amount * self.quantity
|
return self.amount * self.quantity
|
||||||
@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
|
|||||||
def type(self):
|
def type(self):
|
||||||
return _('Transfer')
|
return _('Transfer')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
|
|
||||||
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
|
|
||||||
|
|
||||||
|
|
||||||
class RecurrentTransaction(Transaction):
|
class RecurrentTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = models.ForeignKey(
|
template = models.ForeignKey(
|
||||||
TransactionTemplate,
|
TransactionTemplate,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("recurrent transaction")
|
||||||
|
verbose_name_plural = _("recurrent transactions")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("The destination of this transaction must equal to the destination of the template."))
|
_("The destination of this transaction must equal to the destination of the template."))
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Template')
|
return _('Template')
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("recurrent transaction")
|
|
||||||
verbose_name_plural = _("recurrent transactions")
|
|
||||||
|
|
||||||
|
|
||||||
class SpecialTransaction(Transaction):
|
class SpecialTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to transactions with special notes
|
Special type of :model:`note.Transaction` associated to transactions with special notes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
last_name = models.CharField(
|
last_name = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("name"),
|
verbose_name=_("name"),
|
||||||
@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Special transaction")
|
||||||
|
verbose_name_plural = _("Special transactions")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
||||||
@ -328,11 +335,6 @@ class SpecialTransaction(Transaction):
|
|||||||
raise ValidationError(_("A special transaction is only possible between a"
|
raise ValidationError(_("A special transaction is only possible between a"
|
||||||
" Note associated to a payment method and a User or a Club"))
|
" Note associated to a payment method and a User or a Club"))
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_payment_form(form):
|
def validate_payment_form(form):
|
||||||
"""
|
"""
|
||||||
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
|
|||||||
|
|
||||||
return not error
|
return not error
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Special transaction")
|
|
||||||
verbose_name_plural = _("Special transactions")
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransaction(Transaction):
|
class MembershipTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
membership = models.OneToOneField(
|
membership = models.OneToOneField(
|
||||||
'member.Membership',
|
'member.Membership',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.utils import timezone
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// When a transaction is performed, lock the interface to prevent spam clicks.
|
// When a transaction is performed, lock the interface to prevent spam clicks.
|
||||||
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import html
|
import html
|
||||||
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
name="{{ widget.name }}"
|
name="{{ widget.name }}"
|
||||||
{# Other attributes are loaded #}
|
{# Other attributes are loaded #}
|
||||||
{% for name, value in widget.attrs.items %}
|
{% for name, value in widget.attrs.items %}
|
||||||
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
{% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
|
||||||
{% endfor %}>
|
{% endfor %}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<span class="input-group-text">€</span>
|
<span class="input-group-text">€</span>
|
||||||
|
@ -130,11 +130,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="tab-pane" id="search">
|
<div class="tab-pane" id="search">
|
||||||
<input class="form-control mx-auto d-block mb-3"
|
<input class="form-control mx-auto d-block mb-3"
|
||||||
placeholder="{% trans "Search button..." %}" type="text" id="search-input"/>
|
placeholder="{% trans "Search button..." %}" type="search" id="search-input"/>
|
||||||
<div class="d-inline-flex flex-wrap justify-content-center" id="search-results">
|
<div class="d-inline-flex flex-wrap justify-content-center" id="search-results">
|
||||||
{% for button in search_results %}
|
{% for button in all_buttons %}
|
||||||
{% if button.display %}
|
{% if button.display %}
|
||||||
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
<button class="btn btn-outline-dark rounded-0 flex-fill" hidden
|
||||||
id="search_button{{ button.id }}" name="button" value="{{ button.name }}">
|
id="search_button{{ button.id }}" name="button" value="{{ button.name }}">
|
||||||
{{ button.name }} ({{ button.amount | pretty_money }})
|
{{ button.name }} ({{ button.amount | pretty_money }})
|
||||||
</button>
|
</button>
|
||||||
@ -182,7 +182,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% for button in highlighted %}
|
{% for button in highlighted %}
|
||||||
{% if button.display %}
|
{% if button.display %}
|
||||||
$("#highlighted_button{{ button.id }}").click(function() {
|
document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() {
|
||||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||||
@ -193,7 +193,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
{% for button in category.templates_filtered %}
|
{% for button in category.templates_filtered %}
|
||||||
{% if button.display %}
|
{% if button.display %}
|
||||||
$("#button{{ button.id }}").click(function() {
|
document.getElementById("button{{ button.id }}").addEventListener("click", function() {
|
||||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||||
@ -202,36 +202,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for button in search_results %}
|
{% for button in all_buttons %}
|
||||||
{% if button.display %}
|
{% if button.display %}
|
||||||
$("#search_button{{ button.id }}").click(function() {
|
document.getElementById("search_button{{ button.id }}").addEventListener("click", function() {
|
||||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||||
});
|
});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
searchbar = document.getElementById("search-input")
|
|
||||||
|
|
||||||
const parser = new DOMParser();
|
|
||||||
old_pattern = null;
|
|
||||||
function updateSearch(force = false) {
|
|
||||||
let pattern = searchbar.value
|
|
||||||
if ((pattern === old_pattern || pattern === "") && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("GET", location.pathname + "?search=" +
|
|
||||||
encodeURI(pattern) + "#search", true)
|
|
||||||
xhr.onload = () => {
|
|
||||||
document.getElementById("search-results").innerHTML =
|
|
||||||
parser.parseFromString(xhr.responseText, "text/html").getElementById("search-results").innerHTML
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
searchbar.addEventListener("input", function (e) {
|
|
||||||
debounce(updateSearch)()
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -9,7 +9,7 @@ Ce mail t'a été envoyé parce que le solde de ta Note Kfet
|
|||||||
|
|
||||||
Ton solde actuel est de {{ note.balance|pretty_money }}.
|
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.
|
est inférieur à 0 € depuis plus de 24h.
|
||||||
|
|
||||||
Si tu ne comprends pas ton solde, tu peux consulter ton historique
|
Si tu ne comprends pas ton solde, tu peux consulter ton historique
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django import template
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django import template
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.tests import TestAPI
|
from api.tests import TestAPI
|
||||||
@ -10,7 +10,7 @@ from django.urls import reverse
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
|
|
||||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
|
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
|
||||||
TransactionTemplateViewSet, TransactionViewSet
|
TransactionTemplateViewSet, TransactionViewSet
|
||||||
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@ -190,13 +190,9 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
).order_by('name').all()
|
).order_by('name').all()
|
||||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
||||||
|
|
||||||
if "search" in self.request.GET and self.request.GET["search"]:
|
context['all_buttons'] = TransactionTemplate.objects.filter(
|
||||||
pattern = self.request.GET["search"]
|
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
|
||||||
context['search_results'] = TransactionTemplate.objects.filter(
|
).filter(display=True).order_by('name').all()
|
||||||
name__iregex=pattern
|
|
||||||
).filter(
|
|
||||||
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
|
|
||||||
).filter(display=True).order_by('name').all()
|
|
||||||
|
|
||||||
return context
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'permission.apps.PermissionConfig'
|
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é
|
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
||||||
|
|
||||||
from django.contrib import admin
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import PermissionViewSet, RoleViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
import sys
|
import sys
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
@ -3228,7 +3228,8 @@
|
|||||||
"name": "Pr\u00e9sident\u00b7e de club",
|
"name": "Pr\u00e9sident\u00b7e de club",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
62,
|
62,
|
||||||
142
|
142,
|
||||||
|
135
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
@ -26,6 +26,15 @@ class InstancedPermission:
|
|||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.field:
|
||||||
|
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
||||||
|
else:
|
||||||
|
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
def applies(self, obj, permission_type, field_name=None):
|
def applies(self, obj, permission_type, field_name=None):
|
||||||
"""
|
"""
|
||||||
Returns True if the permission applies to
|
Returns True if the permission applies to
|
||||||
@ -84,21 +93,11 @@ class InstancedPermission:
|
|||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
self.query = Permission._about(self.raw_query, **self.kwargs)
|
self.query = Permission._about(self.raw_query, **self.kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.field:
|
|
||||||
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
|
||||||
else:
|
|
||||||
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.__repr__()
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionMask(models.Model):
|
class PermissionMask(models.Model):
|
||||||
"""
|
"""
|
||||||
Permissions that are hidden behind a mask
|
Permissions that are hidden behind a mask
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rank = models.PositiveSmallIntegerField(
|
rank = models.PositiveSmallIntegerField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('rank'),
|
verbose_name=_('rank'),
|
||||||
@ -110,13 +109,13 @@ class PermissionMask(models.Model):
|
|||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.description
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("permission mask")
|
verbose_name = _("permission mask")
|
||||||
verbose_name_plural = _("permission masks")
|
verbose_name_plural = _("permission masks")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
class Permission(models.Model):
|
class Permission(models.Model):
|
||||||
|
|
||||||
@ -194,16 +193,19 @@ class Permission(models.Model):
|
|||||||
verbose_name = _("permission")
|
verbose_name = _("permission")
|
||||||
verbose_name_plural = _("permissions")
|
verbose_name_plural = _("permissions")
|
||||||
|
|
||||||
def clean(self):
|
def __str__(self):
|
||||||
self.query = json.dumps(json.loads(self.query))
|
return self.description
|
||||||
if self.field and self.type not in {'view', 'change'}:
|
|
||||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
self.full_clean()
|
self.full_clean()
|
||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.query = json.dumps(json.loads(self.query))
|
||||||
|
if self.field and self.type not in {'view', 'change'}:
|
||||||
|
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_f(oper, **kwargs):
|
def compute_f(oper, **kwargs):
|
||||||
if isinstance(oper, list):
|
if isinstance(oper, list):
|
||||||
@ -317,9 +319,6 @@ class Permission(models.Model):
|
|||||||
# query = self._about(query, **kwargs)
|
# query = self._about(query, **kwargs)
|
||||||
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
|
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.description
|
|
||||||
|
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -344,9 +343,9 @@ class Role(models.Model):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("role permissions")
|
verbose_name = _("role permissions")
|
||||||
verbose_name_plural = _("role permissions")
|
verbose_name_plural = _("role permissions")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework.permissions import DjangoObjectPermissions
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from oauth2_provider.oauth2_validators import OAuth2Validator
|
from oauth2_provider.oauth2_validators import OAuth2Validator
|
||||||
from oauth2_provider.scopes import BaseScopes
|
from oauth2_provider.scopes import BaseScopes
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import django_tables2 as tables
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta, date
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import date
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'registration.apps.RegistrationConfig'
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from member.models import Club
|
# from member.models import Club
|
||||||
from note.models import NoteSpecial, Alias
|
from note.models import NoteSpecial, Alias
|
||||||
from note_kfet.inputs import AmountInput
|
from note_kfet.inputs import AmountInput
|
||||||
|
|
||||||
@ -116,11 +116,11 @@ class ValidationForm(forms.Form):
|
|||||||
initial=True,
|
initial=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the bda exists
|
# If the bda exists
|
||||||
if Club.objects.filter(name__iexact="bda").exists():
|
# if Club.objects.filter(name__iexact="bda").exists():
|
||||||
# The user can join the bda club at the inscription
|
# The user can join the bda club at the inscription
|
||||||
join_bda = forms.BooleanField(
|
# join_bda = forms.BooleanField(
|
||||||
label=_("Join BDA Club"),
|
# label=_("Join BDA Club"),
|
||||||
required=False,
|
# required=False,
|
||||||
initial=True,
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import django_tables2 as tables
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py
|
# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py
|
||||||
|
|
||||||
|
@ -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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'treasury.apps.TreasuryConfig'
|
default_app_config = 'treasury.apps.TreasuryConfig'
|
||||||
|
@ -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é
|
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
||||||
|
|
||||||
from django.contrib import admin
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user