mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-08-02 05:34:52 +02:00
Compare commits
55 Commits
v1.0.2
...
c8f7986d5a
Author | SHA1 | Date | |
---|---|---|---|
c8f7986d5a | |||
|
d3a9c442a5
|
||
|
016ab5a9c9
|
||
|
7866ab7ec0
|
||
|
f570ff3cd5
|
||
|
5cb4183e9f
|
||
|
3a20555663
|
||
|
95be0042e9
|
||
|
48880e7fd3
|
||
|
e0030771e4
|
||
|
d47799e6ee
|
||
|
eae091625a
|
||
|
aceb77ffb9
|
||
|
338c94ed05
|
||
|
290848f904 | ||
|
296b94d237 | ||
|
4942553335 | ||
|
c1efb87180 | ||
|
72eead8595 | ||
|
ade7e583e5 | ||
4a8a101822 | |||
dd2cfa6327 | |||
2adf84b7fc | |||
|
2f54e64ea2 | ||
|
8434c0062c | ||
|
6d976f32bf | ||
|
b9d49d53f2 | ||
|
23243e09bb | ||
|
2682e9a610 | ||
|
5635598bbc | ||
|
b58a0c43cd | ||
|
7bd895c1df | ||
|
e5e94c52f2 | ||
|
051591cb7a | ||
|
0e7390b669 | ||
|
fe4363b83d | ||
|
6e80016b38 | ||
|
08e50ffc22 | ||
|
9cb65277f3 | ||
|
224a0fdd8c | ||
|
6dc7604e90 | ||
|
cb7f3c9f18 | ||
|
f910feca9e | ||
|
91f784872c | ||
|
b655135a42 | ||
|
58aa4983e3 | ||
|
6cc3cf4174 | ||
|
2097e67321 | ||
|
d773303d18 | ||
|
3cabcf40e7 | ||
|
bf29efda0a | ||
|
ceccba0d71 | ||
|
3eced33082 | ||
|
acb3fb4a91 | ||
|
420a24ebac |
@@ -38,6 +38,21 @@ py38-django22:
|
|||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
python3-bs4 python3-setuptools tox texlive-xetex
|
||||||
script: tox -e py38-django22
|
script: tox -e py38-django22
|
||||||
|
|
||||||
|
# Debian Bullseye
|
||||||
|
py39-django22:
|
||||||
|
stage: test
|
||||||
|
image: debian:bullseye
|
||||||
|
before_script:
|
||||||
|
- >
|
||||||
|
apt-get update &&
|
||||||
|
apt-get install --no-install-recommends -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 py39-django22
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: debian:buster-backports
|
image: debian:buster-backports
|
||||||
|
10
README.md
10
README.md
@@ -267,14 +267,18 @@ La documentation plus haut niveau sur le développement est disponible sur [le W
|
|||||||
|
|
||||||
### Regénérer les fichiers de traduction
|
### Regénérer les fichiers de traduction
|
||||||
|
|
||||||
Pour regénérer les traductions vous pouvez vous placer à la racine du projet et lancer le script `makemessages`. Il faut penser à ignorer les dossiers ne contenant pas notre code, dont le virtualenv.
|
Pour regénérer les traductions vous pouvez vous placer à la racine du projet et lancer le script `makemessages`.
|
||||||
|
Il faut penser à ignorer les dossiers ne contenant pas notre code, dont le virtualenv.
|
||||||
|
De plus, il faut aussi extraire les variables des fichiers JavaScript.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
django-admin makemessages -i env
|
python3 manage.py makemessages -i env
|
||||||
|
python3 manage.py makemessages -i env -e js -d djangojs
|
||||||
```
|
```
|
||||||
|
|
||||||
Une fois les fichiers édités, vous pouvez compiler les nouvelles traductions avec
|
Une fois les fichiers édités, vous pouvez compiler les nouvelles traductions avec
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
django-admin compilemessages
|
python3 manage.py compilemessages
|
||||||
|
python3 manage.py compilejsmessages
|
||||||
```
|
```
|
||||||
|
@@ -1,4 +1,10 @@
|
|||||||
---
|
---
|
||||||
|
- name: Collect static files
|
||||||
|
command: /var/www/note_kfet/env/bin/python manage.py collectstatic --noinput
|
||||||
|
args:
|
||||||
|
chdir: /var/www/note_kfet
|
||||||
|
become_user: www-data
|
||||||
|
|
||||||
- name: Migrate Django database
|
- name: Migrate Django database
|
||||||
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
||||||
args:
|
args:
|
||||||
@@ -11,14 +17,14 @@
|
|||||||
chdir: /var/www/note_kfet
|
chdir: /var/www/note_kfet
|
||||||
become_user: www-data
|
become_user: www-data
|
||||||
|
|
||||||
|
- name: Compile JavaScript messages
|
||||||
|
command: /var/www/note_kfet/env/bin/python manage.py compilejsmessages
|
||||||
|
args:
|
||||||
|
chdir: /var/www/note_kfet
|
||||||
|
become_user: www-data
|
||||||
|
|
||||||
- name: Install initial fixtures
|
- name: Install initial fixtures
|
||||||
command: /var/www/note_kfet/env/bin/python manage.py loaddata initial
|
command: /var/www/note_kfet/env/bin/python manage.py loaddata initial
|
||||||
args:
|
args:
|
||||||
chdir: /var/www/note_kfet
|
chdir: /var/www/note_kfet
|
||||||
become_user: postgres
|
become_user: postgres
|
||||||
|
|
||||||
- name: Collect static files
|
|
||||||
command: /var/www/note_kfet/env/bin/python manage.py collectstatic --noinput
|
|
||||||
args:
|
|
||||||
chdir: /var/www/note_kfet
|
|
||||||
become_user: www-data
|
|
||||||
|
@@ -15,10 +15,10 @@ class ActivityTypeViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/activity/type/
|
then render it on /api/activity/type/
|
||||||
"""
|
"""
|
||||||
queryset = ActivityType.objects.all()
|
queryset = ActivityType.objects.order_by('id')
|
||||||
serializer_class = ActivityTypeSerializer
|
serializer_class = ActivityTypeSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['name', 'can_invite', ]
|
filterset_fields = ['name', 'manage_entries', 'can_invite', 'guest_entry_fee', ]
|
||||||
|
|
||||||
|
|
||||||
class ActivityViewSet(ReadProtectedModelViewSet):
|
class ActivityViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -27,10 +27,16 @@ class ActivityViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/activity/activity/
|
then render it on /api/activity/activity/
|
||||||
"""
|
"""
|
||||||
queryset = Activity.objects.all()
|
queryset = Activity.objects.order_by('id')
|
||||||
serializer_class = ActivitySerializer
|
serializer_class = ActivitySerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
filterset_fields = ['name', 'description', 'activity_type', ]
|
filterset_fields = ['name', 'description', 'activity_type', 'location', 'creater', 'organizer', 'attendees_club',
|
||||||
|
'date_start', 'date_end', 'valid', 'open', ]
|
||||||
|
search_fields = ['$name', '$description', '$location', '$creater__last_name', '$creater__first_name',
|
||||||
|
'$creater__email', '$creater__note__alias__name', '$creater__note__alias__normalized_name',
|
||||||
|
'$organizer__name', '$organizer__email', '$organizer__note__alias__name',
|
||||||
|
'$organizer__note__alias__normalized_name', '$attendees_club__name', '$attendees_club__email',
|
||||||
|
'$attendees_club__note__alias__name', '$attendees_club__note__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
class GuestViewSet(ReadProtectedModelViewSet):
|
class GuestViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -39,10 +45,13 @@ class GuestViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/activity/guest/
|
then render it on /api/activity/guest/
|
||||||
"""
|
"""
|
||||||
queryset = Guest.objects.all()
|
queryset = Guest.objects.order_by('id')
|
||||||
serializer_class = GuestSerializer
|
serializer_class = GuestSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name',
|
||||||
|
'inviter__alias__normalized_name', ]
|
||||||
|
search_fields = ['$activity__name', '$last_name', '$first_name', '$inviter__user__email', '$inviter__alias__name',
|
||||||
|
'$inviter__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
class EntryViewSet(ReadProtectedModelViewSet):
|
class EntryViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -51,7 +60,9 @@ class EntryViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/activity/entry/
|
then render it on /api/activity/entry/
|
||||||
"""
|
"""
|
||||||
queryset = Entry.objects.all()
|
queryset = Entry.objects.order_by('id')
|
||||||
serializer_class = EntrySerializer
|
serializer_class = EntrySerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
filterset_fields = ['activity', 'time', 'note', 'guest', ]
|
||||||
|
search_fields = ['$activity__name', '$note__user__email', '$note__alias__name', '$note__alias__normalized_name',
|
||||||
|
'$guest__last_name', '$guest__first_name', ]
|
||||||
|
@@ -30,7 +30,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
|
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
|
||||||
})
|
})
|
||||||
.done(function() {
|
.done(function() {
|
||||||
addMsg('Invité supprimé','success');
|
addMsg('{% trans "Guest deleted" %}', 'success');
|
||||||
$("#guests_table").load(location.pathname + " #guests_table");
|
$("#guests_table").load(location.pathname + " #guests_table");
|
||||||
})
|
})
|
||||||
.fail(function(xhr, textStatus, error) {
|
.fail(function(xhr, textStatus, error) {
|
||||||
|
@@ -86,10 +86,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
}).done(function () {
|
}).done(function () {
|
||||||
if (target.hasClass("table-info"))
|
if (target.hasClass("table-info"))
|
||||||
addMsg(
|
addMsg(
|
||||||
"Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.",
|
"{% trans "Entry done, but caution: the user is not a Kfet member." %}",
|
||||||
"warning", 10000);
|
"warning", 10000);
|
||||||
else
|
else
|
||||||
addMsg("Entrée effectuée !", "success", 4000);
|
addMsg("Entry made!", "success", 4000);
|
||||||
reloadTable(true);
|
reloadTable(true);
|
||||||
}).fail(function (xhr) {
|
}).fail(function (xhr) {
|
||||||
errMsg(xhr.responseJSON, 4000);
|
errMsg(xhr.responseJSON, 4000);
|
||||||
@@ -121,10 +121,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
}).done(function () {
|
}).done(function () {
|
||||||
if (target.hasClass("table-info"))
|
if (target.hasClass("table-info"))
|
||||||
addMsg(
|
addMsg(
|
||||||
"Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.",
|
"{% trans "Entry done, but caution: the user is not a Kfet member." %}",
|
||||||
"warning", 10000);
|
"warning", 10000);
|
||||||
else
|
else
|
||||||
addMsg("Entrée effectuée !", "success", 4000);
|
addMsg("{% trans "Entry done!" %}", "success", 4000);
|
||||||
reloadTable(true);
|
reloadTable(true);
|
||||||
}).fail(function (xhr) {
|
}).fail(function (xhr) {
|
||||||
errMsg(xhr.responseJSON, 4000);
|
errMsg(xhr.responseJSON, 4000);
|
||||||
|
@@ -3,13 +3,16 @@
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from api.tests import TestAPI
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from activity.models import Activity, ActivityType, Guest, Entry
|
|
||||||
from member.models import Club
|
from member.models import Club
|
||||||
|
|
||||||
|
from ..api.views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||||
|
from ..models import Activity, ActivityType, Guest, Entry
|
||||||
|
|
||||||
|
|
||||||
class TestActivities(TestCase):
|
class TestActivities(TestCase):
|
||||||
"""
|
"""
|
||||||
@@ -173,3 +176,58 @@ class TestActivities(TestCase):
|
|||||||
"""
|
"""
|
||||||
response = self.client.get(reverse("activity:calendar_ics"))
|
response = self.client.get(reverse("activity:calendar_ics"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestActivityAPI(TestAPI):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.activity = Activity.objects.create(
|
||||||
|
name="Activity",
|
||||||
|
description="This is a test activity\non two very very long lines\nbecause this is very important.",
|
||||||
|
location="Earth",
|
||||||
|
activity_type=ActivityType.objects.get(name="Pot"),
|
||||||
|
creater=self.user,
|
||||||
|
organizer=Club.objects.get(name="Kfet"),
|
||||||
|
attendees_club=Club.objects.get(name="Kfet"),
|
||||||
|
date_start=timezone.now(),
|
||||||
|
date_end=timezone.now() + timedelta(days=2),
|
||||||
|
valid=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.guest = Guest.objects.create(
|
||||||
|
activity=self.activity,
|
||||||
|
inviter=self.user.note,
|
||||||
|
last_name="GUEST",
|
||||||
|
first_name="Guest",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.entry = Entry.objects.create(
|
||||||
|
activity=self.activity,
|
||||||
|
note=self.user.note,
|
||||||
|
guest=self.guest,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_activity_api(self):
|
||||||
|
"""
|
||||||
|
Load Activity API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ActivityViewSet, "/api/activity/activity/")
|
||||||
|
|
||||||
|
def test_activity_type_api(self):
|
||||||
|
"""
|
||||||
|
Load ActivityType API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ActivityTypeViewSet, "/api/activity/type/")
|
||||||
|
|
||||||
|
def test_entry_api(self):
|
||||||
|
"""
|
||||||
|
Load Entry API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(EntryViewSet, "/api/activity/entry/")
|
||||||
|
|
||||||
|
def test_guest_api(self):
|
||||||
|
"""
|
||||||
|
Load Guest API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(GuestViewSet, "/api/activity/guest/")
|
||||||
|
237
apps/api/tests.py
Normal file
237
apps/api/tests.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import json
|
||||||
|
from datetime import datetime, date
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db.models.fields.files import ImageFieldFile
|
||||||
|
from django.test import TestCase
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from member.models import Membership, Club
|
||||||
|
from note.models import NoteClub, NoteUser, Alias, Note
|
||||||
|
from permission.models import PermissionMask, Permission, Role
|
||||||
|
from phonenumbers import PhoneNumber
|
||||||
|
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||||
|
|
||||||
|
from .viewsets import ContentTypeViewSet, UserViewSet
|
||||||
|
|
||||||
|
|
||||||
|
class TestAPI(TestCase):
|
||||||
|
"""
|
||||||
|
Load API pages and check that filters are working.
|
||||||
|
"""
|
||||||
|
fixtures = ('initial', )
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.user = User.objects.create_superuser(
|
||||||
|
username="adminapi",
|
||||||
|
password="adminapi",
|
||||||
|
email="adminapi@example.com",
|
||||||
|
last_name="Admin",
|
||||||
|
first_name="Admin",
|
||||||
|
)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
sess = self.client.session
|
||||||
|
sess["permission_mask"] = 42
|
||||||
|
sess.save()
|
||||||
|
|
||||||
|
def check_viewset(self, viewset, url):
|
||||||
|
"""
|
||||||
|
This function should be called inside a unit test.
|
||||||
|
This loads the viewset and for each filter entry, it checks that the filter is running good.
|
||||||
|
"""
|
||||||
|
resp = self.client.get(url + "?format=json")
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
model = viewset.serializer_class.Meta.model
|
||||||
|
|
||||||
|
if not model.objects.exists(): # pragma: no cover
|
||||||
|
warn(f"Warning: unable to test API filters for the model {model._meta.verbose_name} "
|
||||||
|
"since there is no instance of it.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if hasattr(viewset, "filter_backends"):
|
||||||
|
backends = viewset.filter_backends
|
||||||
|
obj = model.objects.last()
|
||||||
|
|
||||||
|
if DjangoFilterBackend in backends:
|
||||||
|
# Specific search
|
||||||
|
for field in viewset.filterset_fields:
|
||||||
|
obj = self.fix_note_object(obj, field)
|
||||||
|
|
||||||
|
value = self.get_value(obj, field)
|
||||||
|
if value is None: # pragma: no cover
|
||||||
|
warn(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
|
||||||
|
"has not been tested.")
|
||||||
|
continue
|
||||||
|
resp = self.client.get(url + f"?format=json&{field}={quote_plus(str(value))}")
|
||||||
|
self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
|
||||||
|
f"{model._meta.verbose_name} does not work. "
|
||||||
|
f"Given parameter: {value}")
|
||||||
|
content = json.loads(resp.content)
|
||||||
|
self.assertGreater(content["count"], 0, f"The filter {field} for the model "
|
||||||
|
f"{model._meta.verbose_name} does not work. "
|
||||||
|
f"Given parameter: {value}")
|
||||||
|
|
||||||
|
if OrderingFilter in backends:
|
||||||
|
# Ensure that ordering is working well
|
||||||
|
for field in viewset.ordering_fields:
|
||||||
|
resp = self.client.get(url + f"?ordering={field}")
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
resp = self.client.get(url + f"?ordering=-{field}")
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
if SearchFilter in backends:
|
||||||
|
# Basic search
|
||||||
|
for field in viewset.search_fields:
|
||||||
|
obj = self.fix_note_object(obj, field)
|
||||||
|
|
||||||
|
if field[0] == '$' or field[0] == '=':
|
||||||
|
field = field[1:]
|
||||||
|
value = self.get_value(obj, field)
|
||||||
|
if value is None: # pragma: no cover
|
||||||
|
warn(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
|
||||||
|
"has not been tested.")
|
||||||
|
continue
|
||||||
|
resp = self.client.get(url + f"?format=json&search={quote_plus(str(value))}")
|
||||||
|
self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
|
||||||
|
f"{model._meta.verbose_name} does not work. "
|
||||||
|
f"Given parameter: {value}")
|
||||||
|
content = json.loads(resp.content)
|
||||||
|
self.assertGreater(content["count"], 0, f"The filter {field} for the model "
|
||||||
|
f"{model._meta.verbose_name} does not work. "
|
||||||
|
f"Given parameter: {value}")
|
||||||
|
|
||||||
|
self.check_permissions(url, obj)
|
||||||
|
|
||||||
|
def check_permissions(self, url, obj):
|
||||||
|
"""
|
||||||
|
Check that permissions are working
|
||||||
|
"""
|
||||||
|
# Drop rights
|
||||||
|
self.user.is_superuser = False
|
||||||
|
self.user.save()
|
||||||
|
sess = self.client.session
|
||||||
|
sess["permission_mask"] = 0
|
||||||
|
sess.save()
|
||||||
|
|
||||||
|
# Delete user permissions
|
||||||
|
for m in Membership.objects.filter(user=self.user).all():
|
||||||
|
m.roles.clear()
|
||||||
|
m.save()
|
||||||
|
|
||||||
|
# Create a new role, which will have the checking permission
|
||||||
|
role = Role.objects.get_or_create(name="β-tester")[0]
|
||||||
|
role.permissions.clear()
|
||||||
|
role.save()
|
||||||
|
membership = Membership.objects.get_or_create(user=self.user, club=Club.objects.get(name="BDE"))[0]
|
||||||
|
membership.roles.set([role])
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
# Ensure that the access to the object is forbidden without permission
|
||||||
|
resp = self.client.get(url + f"{obj.pk}/")
|
||||||
|
self.assertEqual(resp.status_code, 404, f"Mysterious access to {url}{obj.pk}/ for {obj}")
|
||||||
|
|
||||||
|
obj.refresh_from_db()
|
||||||
|
|
||||||
|
# There are problems with polymorphism
|
||||||
|
if isinstance(obj, Note) and hasattr(obj, "note_ptr"):
|
||||||
|
obj = obj.note_ptr
|
||||||
|
|
||||||
|
mask = PermissionMask.objects.get(rank=0)
|
||||||
|
|
||||||
|
for field in obj._meta.fields:
|
||||||
|
# Build permission query
|
||||||
|
value = self.get_value(obj, field.name)
|
||||||
|
if isinstance(value, date) or isinstance(value, datetime):
|
||||||
|
value = value.isoformat()
|
||||||
|
elif isinstance(value, ImageFieldFile):
|
||||||
|
value = value.name
|
||||||
|
query = json.dumps({field.name: value})
|
||||||
|
|
||||||
|
# Create sample permission
|
||||||
|
permission = Permission.objects.get_or_create(
|
||||||
|
model=ContentType.objects.get_for_model(obj._meta.model),
|
||||||
|
query=query,
|
||||||
|
mask=mask,
|
||||||
|
type="view",
|
||||||
|
permanent=False,
|
||||||
|
description=f"Can view {obj._meta.verbose_name}",
|
||||||
|
)[0]
|
||||||
|
role.permissions.set([permission])
|
||||||
|
role.save()
|
||||||
|
|
||||||
|
# Check that the access is possible
|
||||||
|
resp = self.client.get(url + f"{obj.pk}/")
|
||||||
|
self.assertEqual(resp.status_code, 200, f"Permission {permission.query} is not working "
|
||||||
|
f"for the model {obj._meta.verbose_name}")
|
||||||
|
|
||||||
|
# Restore rights
|
||||||
|
self.user.is_superuser = True
|
||||||
|
self.user.save()
|
||||||
|
sess = self.client.session
|
||||||
|
sess["permission_mask"] = 42
|
||||||
|
sess.save()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_value(obj, key: str):
|
||||||
|
"""
|
||||||
|
Resolve the queryset filter to get the Python value of an object.
|
||||||
|
"""
|
||||||
|
if hasattr(obj, "all"):
|
||||||
|
# obj is a RelatedManager
|
||||||
|
obj = obj.last()
|
||||||
|
|
||||||
|
if obj is None: # pragma: no cover
|
||||||
|
return None
|
||||||
|
|
||||||
|
if '__' not in key:
|
||||||
|
obj = getattr(obj, key)
|
||||||
|
if hasattr(obj, "pk"):
|
||||||
|
return obj.pk
|
||||||
|
elif hasattr(obj, "all"):
|
||||||
|
if not obj.exists(): # pragma: no cover
|
||||||
|
return None
|
||||||
|
return obj.last().pk
|
||||||
|
elif isinstance(obj, bool):
|
||||||
|
return int(obj)
|
||||||
|
elif isinstance(obj, datetime):
|
||||||
|
return obj.isoformat()
|
||||||
|
elif isinstance(obj, PhoneNumber):
|
||||||
|
return obj.raw_input
|
||||||
|
return obj
|
||||||
|
|
||||||
|
key, remaining = key.split('__', 1)
|
||||||
|
return TestAPI.get_value(getattr(obj, key), remaining)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fix_note_object(obj, field):
|
||||||
|
"""
|
||||||
|
When querying an object that has a noteclub or a noteuser field,
|
||||||
|
ensure that the object has a good value.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, Alias):
|
||||||
|
if "noteuser" in field:
|
||||||
|
return NoteUser.objects.last().alias.last()
|
||||||
|
elif "noteclub" in field:
|
||||||
|
return NoteClub.objects.last().alias.last()
|
||||||
|
elif isinstance(obj, Note):
|
||||||
|
if "noteuser" in field:
|
||||||
|
return NoteUser.objects.last()
|
||||||
|
elif "noteclub" in field:
|
||||||
|
return NoteClub.objects.last()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class TestBasicAPI(TestAPI):
|
||||||
|
def test_user_api(self):
|
||||||
|
"""
|
||||||
|
Load the user page.
|
||||||
|
"""
|
||||||
|
self.check_viewset(ContentTypeViewSet, "/api/models/")
|
||||||
|
self.check_viewset(UserViewSet, "/api/user/")
|
@@ -6,6 +6,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from note_kfet.middlewares import get_current_session
|
from note_kfet.middlewares import get_current_session
|
||||||
@@ -48,12 +49,13 @@ class UserViewSet(ReadProtectedModelViewSet):
|
|||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/users/
|
then render it on /api/user/
|
||||||
"""
|
"""
|
||||||
queryset = User.objects.all()
|
queryset = User.objects
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
|
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active',
|
||||||
|
'note__alias__name', 'note__alias__normalized_name', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
@@ -106,7 +108,10 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/users/
|
then render it on /api/models/
|
||||||
"""
|
"""
|
||||||
queryset = ContentType.objects.all()
|
queryset = ContentType.objects.order_by('id')
|
||||||
serializer_class = ContentTypeSerializer
|
serializer_class = ContentTypeSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['id', 'app_label', 'model', ]
|
||||||
|
search_fields = ['$app_label', '$model', ]
|
||||||
|
@@ -15,7 +15,7 @@ class ChangelogViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Changelog` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Changelog` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/logs/
|
then render it on /api/logs/
|
||||||
"""
|
"""
|
||||||
queryset = Changelog.objects.all()
|
queryset = Changelog.objects.order_by('id')
|
||||||
serializer_class = ChangelogSerializer
|
serializer_class = ChangelogSerializer
|
||||||
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
||||||
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
|
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework.filters import SearchFilter
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
from api.viewsets import ReadProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
|
|
||||||
from .serializers import ProfileSerializer, ClubSerializer, MembershipSerializer
|
from .serializers import ProfileSerializer, ClubSerializer, MembershipSerializer
|
||||||
@@ -14,8 +15,15 @@ class ProfileViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/members/profile/
|
then render it on /api/members/profile/
|
||||||
"""
|
"""
|
||||||
queryset = Profile.objects.all()
|
queryset = Profile.objects.order_by('id')
|
||||||
serializer_class = ProfileSerializer
|
serializer_class = ProfileSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['user', 'user__first_name', 'user__last_name', 'user__username', 'user__email',
|
||||||
|
'user__note__alias__name', 'user__note__alias__normalized_name', 'phone_number', "section",
|
||||||
|
'department', 'promotion', 'address', 'paid', 'ml_events_registration', 'ml_sport_registration',
|
||||||
|
'ml_art_registration', 'report_frequency', 'email_confirmed', 'registration_valid', ]
|
||||||
|
search_fields = ['$user__first_name', '$user__last_name', '$user__username', '$user__email',
|
||||||
|
'$user__note__alias__name', '$user__note__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
class ClubViewSet(ReadProtectedModelViewSet):
|
class ClubViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -24,10 +32,13 @@ class ClubViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/members/club/
|
then render it on /api/members/club/
|
||||||
"""
|
"""
|
||||||
queryset = Club.objects.all()
|
queryset = Club.objects.order_by('id')
|
||||||
serializer_class = ClubSerializer
|
serializer_class = ClubSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$name', ]
|
filterset_fields = ['name', 'email', 'note__alias__name', 'note__alias__normalized_name', 'parent_club',
|
||||||
|
'parent_club__name', 'require_memberships', 'membership_fee_paid', 'membership_fee_unpaid',
|
||||||
|
'membership_duration', 'membership_start', 'membership_end', ]
|
||||||
|
search_fields = ['$name', '$email', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
class MembershipViewSet(ReadProtectedModelViewSet):
|
class MembershipViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -36,5 +47,14 @@ class MembershipViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/members/membership/
|
then render it on /api/members/membership/
|
||||||
"""
|
"""
|
||||||
queryset = Membership.objects.all()
|
queryset = Membership.objects.order_by('id')
|
||||||
serializer_class = MembershipSerializer
|
serializer_class = MembershipSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||||
|
filterset_fields = ['club__name', 'club__email', 'club__note__alias__name', 'club__note__alias__normalized_name',
|
||||||
|
'user__username', 'user__last_name', 'user__first_name', 'user__email',
|
||||||
|
'user__note__alias__name', 'user__note__alias__normalized_name',
|
||||||
|
'date_start', 'date_end', 'fee', 'roles', ]
|
||||||
|
ordering_fields = ['id', 'date_start', 'date_end', ]
|
||||||
|
search_fields = ['$club__name', '$club__email', '$club__note__alias__name', '$club__note__alias__normalized_name',
|
||||||
|
'$user__username', '$user__last_name', '$user__first_name', '$user__email',
|
||||||
|
'$user__note__alias__name', '$user__note__alias__normalized_name', '$roles__name', ]
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def give_note_account_permissions(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Automatically manage the membership of the Note account.
|
||||||
|
"""
|
||||||
|
User = apps.get_model("auth", "user")
|
||||||
|
Membership = apps.get_model("member", "membership")
|
||||||
|
Role = apps.get_model("permission", "role")
|
||||||
|
|
||||||
|
note = User.objects.filter(username="note")
|
||||||
|
if not note.exists():
|
||||||
|
# We are in a test environment, don't log error message
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'test':
|
||||||
|
return
|
||||||
|
print("Warning: Note account was not found. The note account was not imported.")
|
||||||
|
print("Make sure you have imported the NK15 database. The new import script handles correctly the permissions.")
|
||||||
|
print("This migration will be ignored, you can re-run it if you forgot the note account or ignore it if you "
|
||||||
|
"don't want this account.")
|
||||||
|
return
|
||||||
|
|
||||||
|
note = note.get()
|
||||||
|
|
||||||
|
# Set for the two clubs a large expiration date and the correct role.
|
||||||
|
for m in Membership.objects.filter(user_id=note.id).all():
|
||||||
|
m.date_end = "3142-12-12"
|
||||||
|
m.roles.set(Role.objects.filter(name="PC Kfet").all())
|
||||||
|
m.save()
|
||||||
|
# By default, the note account is only authorized to be logged from localhost.
|
||||||
|
note.password = "ipbased$127.0.0.1"
|
||||||
|
note.is_active = True
|
||||||
|
note.save()
|
||||||
|
# Ensure that the note of the account is disabled
|
||||||
|
note.note.inactivity_reason = 'forced'
|
||||||
|
note.note.is_active = False
|
||||||
|
note.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('member', '0005_remove_null_tag_on_charfields'),
|
||||||
|
('permission', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(give_note_account_permissions),
|
||||||
|
]
|
@@ -313,6 +313,7 @@ class Membership(models.Model):
|
|||||||
|
|
||||||
roles = models.ManyToManyField(
|
roles = models.ManyToManyField(
|
||||||
"permission.Role",
|
"permission.Role",
|
||||||
|
related_name="memberships",
|
||||||
verbose_name=_("roles"),
|
verbose_name=_("roles"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ function create_alias (e) {
|
|||||||
}).done(function () {
|
}).done(function () {
|
||||||
// Reload table
|
// Reload table
|
||||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||||
addMsg('Alias ajouté', 'success')
|
addMsg(gettext('Alias successfully added'), 'success')
|
||||||
}).fail(function (xhr, _textStatus, _error) {
|
}).fail(function (xhr, _textStatus, _error) {
|
||||||
errMsg(xhr.responseJSON)
|
errMsg(xhr.responseJSON)
|
||||||
})
|
})
|
||||||
@@ -22,7 +22,7 @@ function create_alias (e) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* On click of "delete", delete the alias
|
* On click of "delete", delete the alias
|
||||||
* @param Integer button_id Alias id to remove
|
* @param button_id:Integer Alias id to remove
|
||||||
*/
|
*/
|
||||||
function delete_button (button_id) {
|
function delete_button (button_id) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -30,7 +30,7 @@ function delete_button (button_id) {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg('Alias supprimé', 'success')
|
addMsg(gettext('Alias successfully deleted'), 'success')
|
||||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||||
}).fail(function (xhr, _textStatus, _error) {
|
}).fail(function (xhr, _textStatus, _error) {
|
||||||
errMsg(xhr.responseJSON)
|
errMsg(xhr.responseJSON)
|
||||||
|
@@ -43,8 +43,24 @@ class UserTable(tables.Table):
|
|||||||
|
|
||||||
section = tables.Column(accessor='profile__section')
|
section = tables.Column(accessor='profile__section')
|
||||||
|
|
||||||
|
# Override the column to let replace the URL
|
||||||
|
email = tables.EmailColumn(linkify=lambda record: "mailto:{}".format(record.email))
|
||||||
|
|
||||||
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"))
|
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"))
|
||||||
|
|
||||||
|
def render_email(self, record, value):
|
||||||
|
# Replace the email by a dash if the user can't see the profile detail
|
||||||
|
# Replace also the URL
|
||||||
|
if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile):
|
||||||
|
value = "—"
|
||||||
|
record.email = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def render_section(self, record, value):
|
||||||
|
return value \
|
||||||
|
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \
|
||||||
|
else "—"
|
||||||
|
|
||||||
def render_balance(self, record, value):
|
def render_balance(self, record, value):
|
||||||
return pretty_money(value)\
|
return pretty_money(value)\
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—"
|
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—"
|
||||||
|
@@ -48,7 +48,7 @@
|
|||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
{% trans 'Manage aliases' %} ({{ club.note.alias_set.all|length }})
|
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
@@ -21,10 +21,11 @@
|
|||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
{% trans 'Manage aliases' %} ({{ user_object.note.alias_set.all|length }})
|
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
{% if "member.view_profile"|has_perm:user_object.profile %}
|
||||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.profile.section }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.section }}</dd>
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% if user_object.pk == user.pk %}
|
{% if user_object.pk == user.pk %}
|
||||||
|
@@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% load i18n perms %}
|
{% load i18n perms %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if "member.change_profile_registration_valid"|has_perm:user %}
|
{% if can_manage_registrations %}
|
||||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||||
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
||||||
</a>
|
</a>
|
||||||
|
@@ -5,17 +5,20 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from api.tests import TestAPI
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from member.models import Club, Membership, Profile
|
|
||||||
from note.models import Alias, NoteSpecial
|
from note.models import Alias, NoteSpecial
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
from treasury.models import SogeCredit
|
from treasury.models import SogeCredit
|
||||||
|
|
||||||
|
from ..api.views import ClubViewSet, MembershipViewSet, ProfileViewSet
|
||||||
|
from ..models import Club, Membership, Profile
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Create some users and clubs and test that all pages are rendering properly
|
Create some users and clubs and test that all pages are rendering properly
|
||||||
and that memberships are working.
|
and that memberships are working.
|
||||||
@@ -403,3 +406,46 @@ class TestMemberships(TestCase):
|
|||||||
self.user.password = "custom_nk15$1$" + salt + "|" + hashed
|
self.user.password = "custom_nk15$1$" + salt + "|" + hashed
|
||||||
self.user.save()
|
self.user.save()
|
||||||
self.assertTrue(self.user.check_password(password))
|
self.assertTrue(self.user.check_password(password))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMemberAPI(TestAPI):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.user.profile.registration_valid = True
|
||||||
|
self.user.profile.email_confirmed = True
|
||||||
|
self.user.profile.phone_number = "0600000000"
|
||||||
|
self.user.profile.section = "1A0"
|
||||||
|
self.user.profile.department = "A0"
|
||||||
|
self.user.profile.address = "Earth"
|
||||||
|
self.user.profile.save()
|
||||||
|
|
||||||
|
self.club = Club.objects.create(
|
||||||
|
name="totoclub",
|
||||||
|
parent_club=Club.objects.get(name="BDE"),
|
||||||
|
membership_start=date(year=1970, month=1, day=1),
|
||||||
|
membership_end=date(year=2040, month=1, day=1),
|
||||||
|
membership_duration=365 * 10,
|
||||||
|
)
|
||||||
|
self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
|
||||||
|
self.membership = Membership.objects.create(user=self.user, club=self.club)
|
||||||
|
self.membership.roles.add(Role.objects.get(name="Bureau de club"))
|
||||||
|
self.membership.save()
|
||||||
|
|
||||||
|
def test_club_api(self):
|
||||||
|
"""
|
||||||
|
Load Club API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ClubViewSet, "/api/members/club/")
|
||||||
|
|
||||||
|
def test_profile_api(self):
|
||||||
|
"""
|
||||||
|
Load Profile API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ProfileViewSet, "/api/members/profile/")
|
||||||
|
|
||||||
|
def test_membership_api(self):
|
||||||
|
"""
|
||||||
|
Load Membership API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(MembershipViewSet, "/api/members/membership/")
|
||||||
|
@@ -70,6 +70,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
form.fields['email'].required = True
|
form.fields['email'].required = True
|
||||||
form.fields['email'].help_text = _("This address must be valid.")
|
form.fields['email'].help_text = _("This address must be valid.")
|
||||||
|
|
||||||
|
if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile):
|
||||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||||
data=self.request.POST if self.request.POST else None)
|
data=self.request.POST if self.request.POST else None)
|
||||||
if not self.object.profile.report_frequency:
|
if not self.object.profile.report_frequency:
|
||||||
@@ -234,6 +235,13 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\
|
||||||
|
.filter(profile__registration_valid=False)
|
||||||
|
context["can_manage_registrations"] = pre_registered_users.exists()
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
"""
|
"""
|
||||||
@@ -247,8 +255,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
|
context["aliases"] = AliasTable(
|
||||||
.filter_queryset(self.request.user, Alias, "view")).all())
|
note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||||
note=context["object"].note,
|
note=context["object"].note,
|
||||||
name="",
|
name="",
|
||||||
@@ -450,8 +458,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
|
context["aliases"] = AliasTable(note.alias.filter(
|
||||||
.filter_queryset(self.request.user, Alias, "view")).all())
|
PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||||
note=context["object"].note,
|
note=context["object"].note,
|
||||||
name="",
|
name="",
|
||||||
@@ -670,11 +678,13 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
|
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
|
||||||
if not last_name:
|
if not last_name:
|
||||||
form.add_error('last_name', _("This field is required."))
|
form.add_error('last_name', _("This field is required."))
|
||||||
|
error = True
|
||||||
if not first_name:
|
if not first_name:
|
||||||
form.add_error('first_name', _("This field is required."))
|
form.add_error('first_name', _("This field is required."))
|
||||||
|
error = True
|
||||||
if not bank and credit_type.special_type == "Chèque":
|
if not bank and credit_type.special_type == "Chèque":
|
||||||
form.add_error('bank', _("This field is required."))
|
form.add_error('bank', _("This field is required."))
|
||||||
return self.form_invalid(form)
|
error = True
|
||||||
|
|
||||||
return not error
|
return not error
|
||||||
|
|
||||||
|
@@ -15,29 +15,37 @@ from permission.backends import PermissionBackend
|
|||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||||
from ..models.notes import Note, Alias
|
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial
|
||||||
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
|
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
|
||||||
|
|
||||||
|
|
||||||
class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
The djangorestframework plugin will get all `Note` objects (with polymorhism), serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Note` objects (with polymorhism),
|
||||||
|
serialize it to JSON with the given serializer,
|
||||||
then render it on /api/note/note/
|
then render it on /api/note/note/
|
||||||
"""
|
"""
|
||||||
queryset = Note.objects.all()
|
queryset = Note.objects.order_by('id')
|
||||||
serializer_class = NotePolymorphicSerializer
|
serializer_class = NotePolymorphicSerializer
|
||||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||||
filterset_fields = ['polymorphic_ctype', 'is_active', ]
|
filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ]
|
||||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
|
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model',
|
||||||
ordering_fields = ['alias__name', 'alias__normalized_name']
|
'$noteuser__user__last_name', '$noteuser__user__first_name', '$noteuser__user__email',
|
||||||
|
'$noteuser__user__email', '$noteclub__club__email', ]
|
||||||
|
ordering_fields = ['alias__name', 'alias__normalized_name', 'balance', 'created_at', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Parse query and apply filters.
|
Parse query and apply filters.
|
||||||
:return: The filtered set of requested notes
|
:return: The filtered set of requested notes
|
||||||
"""
|
"""
|
||||||
queryset = super().get_queryset().distinct()
|
user = self.request.user
|
||||||
|
get_current_session().setdefault("permission_mask", 42)
|
||||||
|
queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view")
|
||||||
|
| PermissionBackend.filter_queryset(user, NoteUser, "view")
|
||||||
|
| PermissionBackend.filter_queryset(user, NoteClub, "view")
|
||||||
|
| PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct()
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", ".*")
|
alias = self.request.query_params.get("alias", ".*")
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
@@ -55,12 +63,12 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/aliases/
|
then render it on /api/aliases/
|
||||||
"""
|
"""
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects
|
||||||
serializer_class = AliasSerializer
|
serializer_class = AliasSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
filterset_fields = ['note']
|
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||||
ordering_fields = ['name', 'normalized_name']
|
ordering_fields = ['name', 'normalized_name', ]
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
serializer_class = self.serializer_class
|
serializer_class = self.serializer_class
|
||||||
@@ -106,12 +114,12 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects
|
||||||
serializer_class = ConsumerSerializer
|
serializer_class = ConsumerSerializer
|
||||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
filterset_fields = ['note']
|
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||||
ordering_fields = ['name', 'normalized_name']
|
ordering_fields = ['name', 'normalized_name', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
@@ -157,10 +165,11 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `TemplateCategory` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `TemplateCategory` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/note/transaction/category/
|
then render it on /api/note/transaction/category/
|
||||||
"""
|
"""
|
||||||
queryset = TemplateCategory.objects.order_by("name").all()
|
queryset = TemplateCategory.objects.order_by('name')
|
||||||
serializer_class = TemplateCategorySerializer
|
serializer_class = TemplateCategorySerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$name', ]
|
filterset_fields = ['name', 'templates', 'templates__name']
|
||||||
|
search_fields = ['$name', '$templates__name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||||
@@ -169,11 +178,12 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
|||||||
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/note/transaction/template/
|
then render it on /api/note/transaction/template/
|
||||||
"""
|
"""
|
||||||
queryset = TransactionTemplate.objects.order_by("name").all()
|
queryset = TransactionTemplate.objects.order_by('name')
|
||||||
serializer_class = TransactionTemplateSerializer
|
serializer_class = TransactionTemplateSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ]
|
||||||
search_fields = ['$name', ]
|
search_fields = ['$name', '$category__name', ]
|
||||||
|
ordering_fields = ['amount', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionViewSet(ReadProtectedModelViewSet):
|
class TransactionViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -182,13 +192,17 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/note/transaction/transaction/
|
then render it on /api/note/transaction/transaction/
|
||||||
"""
|
"""
|
||||||
queryset = Transaction.objects.order_by("-created_at").all()
|
queryset = Transaction.objects.order_by('-created_at')
|
||||||
serializer_class = TransactionPolymorphicSerializer
|
serializer_class = TransactionPolymorphicSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
filterset_fields = ["source", "source_alias", "destination", "destination_alias", "quantity",
|
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
|
||||||
"polymorphic_ctype", "amount", "created_at", ]
|
'destination', 'destination_alias', 'destination__alias__name',
|
||||||
search_fields = ['$reason', ]
|
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount',
|
||||||
ordering_fields = ['created_at', 'amount']
|
'created_at', 'valid', 'invalidity_reason', ]
|
||||||
|
search_fields = ['$reason', '$source_alias', '$source__alias__name', '$source__alias__normalized_name',
|
||||||
|
'$destination_alias', '$destination__alias__name', '$destination__alias__normalized_name',
|
||||||
|
'$invalidity_reason', ]
|
||||||
|
ordering_fields = ['created_at', 'amount', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
@@ -248,6 +248,7 @@ class Alias(models.Model):
|
|||||||
note = models.ForeignKey(
|
note = models.ForeignKey(
|
||||||
Note,
|
Note,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
related_name="alias",
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@@ -222,17 +222,14 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
|||||||
if (!isNaN(source.balance)) {
|
if (!isNaN(source.balance)) {
|
||||||
const newBalance = source.balance - quantity * amount
|
const newBalance = source.balance - quantity * amount
|
||||||
if (newBalance <= -5000) {
|
if (newBalance <= -5000) {
|
||||||
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||||
'succès, mais la note émettrice ' + source_alias + ' est en négatif sévère.',
|
'but the emitter note %s is very negative.', [source_alias, source_alias])), 'danger', 30000)
|
||||||
'danger', 30000)
|
|
||||||
} else if (newBalance < 0) {
|
} else if (newBalance < 0) {
|
||||||
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||||
'succès, mais la note émettrice ' + source_alias + ' est en négatif.',
|
'but the emitter note %s is negative.', [source_alias, source_alias])), 'warning', 30000)
|
||||||
'warning', 30000)
|
|
||||||
}
|
}
|
||||||
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
||||||
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.', [source_alias])), 'danger', 30000)
|
||||||
'danger', 30000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset()
|
reset()
|
||||||
@@ -253,7 +250,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
|||||||
template: template
|
template: template
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
reset()
|
reset()
|
||||||
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", 'danger', 10000)
|
addMsg(gettext("The transaction couldn't be validated because of insufficient balance."), 'danger', 10000)
|
||||||
}).fail(function () {
|
}).fail(function () {
|
||||||
reset()
|
reset()
|
||||||
errMsg(e.responseJSON)
|
errMsg(e.responseJSON)
|
||||||
|
@@ -239,20 +239,20 @@ $('#btn_transfer').click(function () {
|
|||||||
|
|
||||||
if (!amount_field.val() || isNaN(amount_field.val()) || amount_field.val() <= 0) {
|
if (!amount_field.val() || isNaN(amount_field.val()) || amount_field.val() <= 0) {
|
||||||
amount_field.addClass('is-invalid')
|
amount_field.addClass('is-invalid')
|
||||||
$('#amount-required').html('<strong>Ce champ est requis et doit comporter un nombre décimal strictement positif.</strong>')
|
$('#amount-required').html('<strong>' + gettext('This field is required and must contain a decimal positive number.') + '</strong>')
|
||||||
error = true
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = Math.floor(100 * amount_field.val())
|
const amount = Math.floor(100 * amount_field.val())
|
||||||
if (amount > 2147483647) {
|
if (amount > 2147483647) {
|
||||||
amount_field.addClass('is-invalid')
|
amount_field.addClass('is-invalid')
|
||||||
$('#amount-required').html('<strong>Le montant ne doit pas excéder 21474836.47 €.</strong>')
|
$('#amount-required').html('<strong>' + gettext('The amount must stay under 21,474,836.47 €.') + '</strong>')
|
||||||
error = true
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reason_field.val() && $('#type_transfer').is(':checked')) {
|
if (!reason_field.val() && $('#type_transfer').is(':checked')) {
|
||||||
reason_field.addClass('is-invalid')
|
reason_field.addClass('is-invalid')
|
||||||
$('#reason-required').html('<strong>Ce champ est requis.</strong>')
|
$('#reason-required').html('<strong>' + gettext('This field is required.') + '</strong>')
|
||||||
error = true
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,9 +278,8 @@ $('#btn_transfer').click(function () {
|
|||||||
[...sources_notes_display].forEach(function (source) {
|
[...sources_notes_display].forEach(function (source) {
|
||||||
[...dests_notes_display].forEach(function (dest) {
|
[...dests_notes_display].forEach(function (dest) {
|
||||||
if (source.note.id === dest.note.id) {
|
if (source.note.id === dest.note.id) {
|
||||||
addMsg('Attention : la transaction de ' + pretty_money(amount) + ' de la note ' + source.name +
|
addMsg(interpolate(gettext('Warning: the transaction of %s from %s to %s was not made because ' +
|
||||||
' vers la note ' + dest.name + " n'a pas été faite car il s'agit de la même note au départ" +
|
'it is the same source and destination note.'), [pretty_money(amount), source.name, dest.name]), 'warning', 10000)
|
||||||
" et à l'arrivée.", 'warning', 10000)
|
|
||||||
LOCK = false
|
LOCK = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -300,43 +299,35 @@ $('#btn_transfer').click(function () {
|
|||||||
destination_alias: dest.name
|
destination_alias: dest.name
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
if (source.note.membership && source.note.membership.date_end < new Date().toISOString()) {
|
if (source.note.membership && source.note.membership.date_end < new Date().toISOString()) {
|
||||||
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
||||||
'danger', 30000)
|
|
||||||
}
|
}
|
||||||
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
||||||
addMsg('Attention : la note destination ' + dest.name + " n'est plus adhérente.",
|
addMsg(interpolate(gettext('Warning, the destination note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
||||||
'danger', 30000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(source.note.balance)) {
|
if (!isNaN(source.note.balance)) {
|
||||||
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
|
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
|
||||||
if (newBalance <= -5000) {
|
if (newBalance <= -5000) {
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
|
||||||
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
|
||||||
'mais la note émettrice est en négatif sévère.', 'danger', 10000)
|
|
||||||
reset()
|
reset()
|
||||||
return
|
return
|
||||||
} else if (newBalance < 0) {
|
} else if (newBalance < 0) {
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is negative.'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
|
||||||
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
|
||||||
'mais la note émettrice est en négatif.', 'warning', 10000)
|
|
||||||
reset()
|
reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s succeed!'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name]), 'success', 10000)
|
||||||
' vers la note ' + dest.name + ' a été fait avec succès !', 'success', 10000)
|
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
}).fail(function (err) { // do it again but valid = false
|
}).fail(function (err) { // do it again but valid = false
|
||||||
const errObj = JSON.parse(err.responseText)
|
const errObj = JSON.parse(err.responseText)
|
||||||
if (errObj.non_field_errors) {
|
if (errObj.non_field_errors) {
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, errObj.non_field_errors]), 'danger')
|
||||||
' vers la note ' + dest.name + ' a échoué : ' + errObj.non_field_errors, 'danger')
|
|
||||||
LOCK = false
|
LOCK = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -356,17 +347,15 @@ $('#btn_transfer').click(function () {
|
|||||||
destination: dest.note.id,
|
destination: dest.note.id,
|
||||||
destination_alias: dest.name
|
destination_alias: dest.name
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, gettext('insufficient funds')]), 'danger', 10000)
|
||||||
' vers la note ' + dest.name + ' a échoué : Solde insuffisant', 'danger', 10000)
|
|
||||||
reset()
|
reset()
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
const errObj = JSON.parse(err.responseText)
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error) { error = err.responseText }
|
if (!error) { error = err.responseText }
|
||||||
addMsg('Le transfert de ' +
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, error]), 'danger')
|
||||||
' vers la note ' + dest.name + ' a échoué : ' + error, 'danger')
|
|
||||||
LOCK = false
|
LOCK = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -412,14 +401,14 @@ $('#btn_transfer').click(function () {
|
|||||||
first_name: $('#first_name').val(),
|
first_name: $('#first_name').val(),
|
||||||
bank: $('#bank').val()
|
bank: $('#bank').val()
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg('Le crédit/retrait a bien été effectué !', 'success', 10000)
|
addMsg(gettext('Credit/debit succeed!'), 'success', 10000)
|
||||||
if (user_note.membership && user_note.membership.date_end < new Date().toISOString()) { addMsg('Attention : la note ' + alias + " n'est plus adhérente.", 'danger', 10000) }
|
if (user_note.membership && user_note.membership.date_end < new Date().toISOString()) { addMsg(gettext('Warning, the emitter note %s is no more a BDE member.'), 'danger', 10000) }
|
||||||
reset()
|
reset()
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
const errObj = JSON.parse(err.responseText)
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error) { error = err.responseText }
|
if (!error) { error = err.responseText }
|
||||||
addMsg('Le crédit/retrait a échoué : ' + error, 'danger', 10000)
|
addMsg(interpolate(gettext('Credit/debit failed: %s'), [error]), 'danger', 10000)
|
||||||
LOCK = false
|
LOCK = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,20 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 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 member.models import Club, Membership
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from member.models import Club, Membership
|
from django.utils import timezone
|
||||||
from note.models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
|
||||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias
|
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
|
|
||||||
|
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
|
||||||
|
TransactionTemplateViewSet, TransactionViewSet
|
||||||
|
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||||
|
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
||||||
|
|
||||||
|
|
||||||
class TestTransactions(TestCase):
|
class TestTransactions(TestCase):
|
||||||
fixtures = ('initial', )
|
fixtures = ('initial', )
|
||||||
@@ -297,8 +302,8 @@ class TestTransactions(TestCase):
|
|||||||
|
|
||||||
def test_render_search_transactions(self):
|
def test_render_search_transactions(self):
|
||||||
response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
|
response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
|
||||||
source=self.second_user.note.alias_set.first().id,
|
source=self.second_user.note.alias.first().id,
|
||||||
destination=self.user.note.alias_set.first().id,
|
destination=self.user.note.alias.first().id,
|
||||||
type=[ContentType.objects.get_for_model(Transaction).id],
|
type=[ContentType.objects.get_for_model(Transaction).id],
|
||||||
reason="test",
|
reason="test",
|
||||||
valid=True,
|
valid=True,
|
||||||
@@ -363,3 +368,69 @@ class TestTransactions(TestCase):
|
|||||||
self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
|
self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
|
||||||
response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
|
response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNoteAPI(TestAPI):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
|
||||||
|
membership.roles.add(Role.objects.get(name="Respo info"))
|
||||||
|
membership.save()
|
||||||
|
Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
|
||||||
|
self.user.note.last_negative = timezone.now()
|
||||||
|
self.user.note.save()
|
||||||
|
|
||||||
|
self.transaction = Transaction.objects.create(
|
||||||
|
source=Note.objects.first(),
|
||||||
|
destination=self.user.note,
|
||||||
|
amount=4200,
|
||||||
|
reason="Test transaction",
|
||||||
|
)
|
||||||
|
self.user.note.refresh_from_db()
|
||||||
|
Alias.objects.create(note=self.user.note, name="I am a ¢omplex alias")
|
||||||
|
|
||||||
|
self.category = TemplateCategory.objects.create(name="Test")
|
||||||
|
self.template = TransactionTemplate.objects.create(
|
||||||
|
name="Test",
|
||||||
|
destination=Club.objects.get(name="BDE").note,
|
||||||
|
category=self.category,
|
||||||
|
amount=100,
|
||||||
|
description="Test template",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_alias_api(self):
|
||||||
|
"""
|
||||||
|
Load Alias API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(AliasViewSet, "/api/note/alias/")
|
||||||
|
|
||||||
|
def test_consumer_api(self):
|
||||||
|
"""
|
||||||
|
Load Consumer API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ConsumerViewSet, "/api/note/consumer/")
|
||||||
|
|
||||||
|
def test_note_api(self):
|
||||||
|
"""
|
||||||
|
Load Note API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(NotePolymorphicViewSet, "/api/note/note/")
|
||||||
|
|
||||||
|
def test_template_category_api(self):
|
||||||
|
"""
|
||||||
|
Load TemplateCategory API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(TemplateCategoryViewSet, "/api/note/transaction/category/")
|
||||||
|
|
||||||
|
def test_transaction_template_api(self):
|
||||||
|
"""
|
||||||
|
Load TemplateTemplate API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(TransactionTemplateViewSet, "/api/note/transaction/template/")
|
||||||
|
|
||||||
|
def test_transaction_api(self):
|
||||||
|
"""
|
||||||
|
Load Transaction API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(TransactionViewSet, "/api/note/transaction/transaction/")
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 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 api.viewsets import ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
|
|
||||||
from .serializers import PermissionSerializer, RoleSerializer
|
from .serializers import PermissionSerializer, RoleSerializer
|
||||||
from ..models import Permission, Role
|
from ..models import Permission, Role
|
||||||
@@ -14,10 +15,11 @@ class PermissionViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Permission` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Permission` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/permission/permission/
|
then render it on /api/permission/permission/
|
||||||
"""
|
"""
|
||||||
queryset = Permission.objects.all()
|
queryset = Permission.objects.order_by('id')
|
||||||
serializer_class = PermissionSerializer
|
serializer_class = PermissionSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
filterset_fields = ['model', 'type', ]
|
filterset_fields = ['model', 'type', 'query', 'mask', 'field', 'permanent', ]
|
||||||
|
search_fields = ['$model__name', '$query', '$description', ]
|
||||||
|
|
||||||
|
|
||||||
class RoleViewSet(ReadOnlyProtectedModelViewSet):
|
class RoleViewSet(ReadOnlyProtectedModelViewSet):
|
||||||
@@ -26,7 +28,8 @@ class RoleViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `RolePermission` objects, serialize it to JSON with the given serializer
|
The djangorestframework plugin will get all `RolePermission` objects, serialize it to JSON with the given serializer
|
||||||
then render it on /api/permission/roles/
|
then render it on /api/permission/roles/
|
||||||
"""
|
"""
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.order_by('id')
|
||||||
serializer_class = RoleSerializer
|
serializer_class = RoleSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
filterset_fields = ['role', ]
|
filterset_fields = ['name', 'permissions', 'for_club', 'memberships__user', ]
|
||||||
|
SearchFilter = ['$name', '$for_club__name', ]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import sys
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
@@ -38,6 +38,10 @@ def memoize(f):
|
|||||||
|
|
||||||
nonlocal last_collect
|
nonlocal last_collect
|
||||||
|
|
||||||
|
if "test" in sys.argv:
|
||||||
|
# In a test environment, don't memoize permissions
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
if time() - last_collect > 60:
|
if time() - last_collect > 60:
|
||||||
# Clear cache
|
# Clear cache
|
||||||
collect()
|
collect()
|
||||||
|
@@ -799,12 +799,12 @@
|
|||||||
"member",
|
"member",
|
||||||
"membership"
|
"membership"
|
||||||
],
|
],
|
||||||
"query": "{\"club\": [\"club\"]}",
|
"query": "{}",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 3,
|
"mask": 3,
|
||||||
"field": "roles",
|
"field": "roles",
|
||||||
"permanent": false,
|
"permanent": false,
|
||||||
"description": "Modifier les rôles d'un adhérent d'un club"
|
"description": "Modifier les rôles d'une adhésion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -819,7 +819,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier son profil"
|
"description": "Modifier son profil"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2081,7 +2081,7 @@
|
|||||||
],
|
],
|
||||||
"query": "{}",
|
"query": "{}",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 2,
|
||||||
"field": "invalidity_reason",
|
"field": "invalidity_reason",
|
||||||
"permanent": false,
|
"permanent": false,
|
||||||
"description": "Modifier la raison d'invalidité d'une transaction"
|
"description": "Modifier la raison d'invalidité d'une transaction"
|
||||||
@@ -2807,6 +2807,70 @@
|
|||||||
"description": "Voir ses propres alias, pour toujours"
|
"description": "Voir ses propres alias, pour toujours"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 180,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"auth",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"query": "{\"profile__registration_valid\": false}",
|
||||||
|
"type": "view",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Voir n'importe quel utilisateur non encore inscrit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 181,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"member",
|
||||||
|
"profile"
|
||||||
|
],
|
||||||
|
"query": "{\"registration_valid\": false}",
|
||||||
|
"type": "view",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Voir n'importe quel profil non encore inscrit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 182,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"auth",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
|
||||||
|
"type": "view",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 183,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"note",
|
||||||
|
"note"
|
||||||
|
],
|
||||||
|
"query": "{}",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "display_image",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Changer l'image de n'importe quelle note"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@@ -2939,14 +3003,14 @@
|
|||||||
62,
|
62,
|
||||||
127,
|
127,
|
||||||
133,
|
133,
|
||||||
135,
|
|
||||||
136,
|
136,
|
||||||
141,
|
141,
|
||||||
142,
|
142,
|
||||||
150,
|
150,
|
||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
168
|
168,
|
||||||
|
182
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3022,7 +3086,8 @@
|
|||||||
175,
|
175,
|
||||||
176,
|
176,
|
||||||
177,
|
177,
|
||||||
178
|
178,
|
||||||
|
183
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3205,7 +3270,12 @@
|
|||||||
175,
|
175,
|
||||||
176,
|
176,
|
||||||
177,
|
177,
|
||||||
178
|
178,
|
||||||
|
179,
|
||||||
|
180,
|
||||||
|
181,
|
||||||
|
182,
|
||||||
|
183
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3239,7 +3309,12 @@
|
|||||||
170,
|
170,
|
||||||
171,
|
171,
|
||||||
176,
|
176,
|
||||||
177
|
177,
|
||||||
|
178,
|
||||||
|
179,
|
||||||
|
180,
|
||||||
|
181,
|
||||||
|
182
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3402,7 +3477,6 @@
|
|||||||
135,
|
135,
|
||||||
136,
|
136,
|
||||||
137,
|
137,
|
||||||
138,
|
|
||||||
139,
|
139,
|
||||||
140,
|
140,
|
||||||
143,
|
143,
|
||||||
@@ -3415,6 +3489,41 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.role",
|
||||||
|
"pk": 20,
|
||||||
|
"fields": {
|
||||||
|
"for_club": 2,
|
||||||
|
"name": "PC Kfet",
|
||||||
|
"permissions": [
|
||||||
|
6,
|
||||||
|
22,
|
||||||
|
24,
|
||||||
|
25,
|
||||||
|
26,
|
||||||
|
27,
|
||||||
|
30,
|
||||||
|
49,
|
||||||
|
50,
|
||||||
|
55,
|
||||||
|
56,
|
||||||
|
57,
|
||||||
|
58,
|
||||||
|
137,
|
||||||
|
143,
|
||||||
|
147,
|
||||||
|
150,
|
||||||
|
166,
|
||||||
|
167,
|
||||||
|
168,
|
||||||
|
176,
|
||||||
|
177,
|
||||||
|
180,
|
||||||
|
181,
|
||||||
|
182
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "wei.weirole",
|
"model": "wei.weirole",
|
||||||
"pk": 12,
|
"pk": 12,
|
||||||
|
@@ -45,6 +45,7 @@ class InstancedPermission:
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
sid = transaction.savepoint()
|
sid = transaction.savepoint()
|
||||||
for o in self.model.model_class().objects.filter(pk=0).all():
|
for o in self.model.model_class().objects.filter(pk=0).all():
|
||||||
|
o._no_signal = True
|
||||||
o._force_delete = True
|
o._force_delete = True
|
||||||
Model.delete(o)
|
Model.delete(o)
|
||||||
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
|
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
|
||||||
@@ -62,10 +63,6 @@ class InstancedPermission:
|
|||||||
obj._no_signal = True
|
obj._no_signal = True
|
||||||
Model.save(obj, force_insert=True)
|
Model.save(obj, force_insert=True)
|
||||||
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
|
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
|
||||||
# Delete testing object
|
|
||||||
obj._no_signal = True
|
|
||||||
obj._force_delete = True
|
|
||||||
Model.delete(obj)
|
|
||||||
transaction.savepoint_rollback(sid)
|
transaction.savepoint_rollback(sid)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@@ -5,7 +5,6 @@ from django.contrib.auth.models import AnonymousUser
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
from django import template
|
from django import template
|
||||||
from note.models import Transaction
|
|
||||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
@@ -25,21 +24,6 @@ def not_empty_model_list(model_name):
|
|||||||
return qs.exists()
|
return qs.exists()
|
||||||
|
|
||||||
|
|
||||||
@stringfilter
|
|
||||||
def not_empty_model_change_list(model_name):
|
|
||||||
"""
|
|
||||||
Return True if and only if the current user has right to change any object of the given model.
|
|
||||||
"""
|
|
||||||
user = get_current_authenticated_user()
|
|
||||||
session = get_current_session()
|
|
||||||
if user is None or isinstance(user, AnonymousUser):
|
|
||||||
return False
|
|
||||||
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
|
||||||
return True
|
|
||||||
qs = model_list(model_name, "change")
|
|
||||||
return qs.exists()
|
|
||||||
|
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def model_list(model_name, t="view", fetch=True):
|
def model_list(model_name, t="view", fetch=True):
|
||||||
"""
|
"""
|
||||||
@@ -68,33 +52,8 @@ def has_perm(perm, obj):
|
|||||||
return PermissionBackend.check_perm(get_current_authenticated_user(), perm, obj)
|
return PermissionBackend.check_perm(get_current_authenticated_user(), perm, obj)
|
||||||
|
|
||||||
|
|
||||||
def can_create_transaction():
|
|
||||||
"""
|
|
||||||
:return: True iff the authenticated user can create a transaction.
|
|
||||||
"""
|
|
||||||
user = get_current_authenticated_user()
|
|
||||||
session = get_current_session()
|
|
||||||
if user is None or isinstance(user, AnonymousUser):
|
|
||||||
return False
|
|
||||||
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
|
||||||
return True
|
|
||||||
if session.get("can_create_transaction", None):
|
|
||||||
return session.get("can_create_transaction", None) == 1
|
|
||||||
|
|
||||||
empty_transaction = Transaction(
|
|
||||||
source=user.note,
|
|
||||||
destination=user.note,
|
|
||||||
quantity=1,
|
|
||||||
amount=0,
|
|
||||||
reason="Check permissions",
|
|
||||||
)
|
|
||||||
session["can_create_transaction"] = PermissionBackend.check_perm(user, "note.add_transaction", empty_transaction)
|
|
||||||
return session.get("can_create_transaction") == 1
|
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
register.filter('not_empty_model_list', not_empty_model_list)
|
register.filter('not_empty_model_list', not_empty_model_list)
|
||||||
register.filter('not_empty_model_change_list', not_empty_model_change_list)
|
|
||||||
register.filter('model_list', model_list)
|
register.filter('model_list', model_list)
|
||||||
register.filter('model_list_length', model_list_length)
|
register.filter('model_list_length', model_list_length)
|
||||||
register.filter('has_perm', has_perm)
|
register.filter('has_perm', has_perm)
|
||||||
|
@@ -78,7 +78,7 @@ class PermissionQueryTestCase(TestCase):
|
|||||||
query = instanced.query
|
query = instanced.query
|
||||||
model = perm.model.model_class()
|
model = perm.model.model_class()
|
||||||
model.objects.filter(query).all()
|
model.objects.filter(query).all()
|
||||||
except (FieldError, AttributeError, ValueError, TypeError, JSONDecodeError):
|
except (FieldError, AttributeError, ValueError, TypeError, JSONDecodeError): # pragma: no cover
|
||||||
print("Query error for permission", perm)
|
print("Query error for permission", perm)
|
||||||
print("Query:", perm.query)
|
print("Query:", perm.query)
|
||||||
if instanced.query:
|
if instanced.query:
|
||||||
|
@@ -51,8 +51,10 @@ class ProtectQuerysetMixin:
|
|||||||
# No worry if the user change the hidden fields: a 403 error will be performed if the user tries to make
|
# No worry if the user change the hidden fields: a 403 error will be performed if the user tries to make
|
||||||
# a custom request.
|
# a custom request.
|
||||||
# We could also delete the field, but some views might be affected.
|
# We could also delete the field, but some views might be affected.
|
||||||
|
meta = form.instance._meta
|
||||||
for key in form.base_fields:
|
for key in form.base_fields:
|
||||||
if not PermissionBackend.check_perm(self.request.user, "wei.change_weiregistration_" + key, self.object):
|
if not PermissionBackend.check_perm(self.request.user,
|
||||||
|
f"{meta.app_label}.change_{meta.model_name}_" + key, self.object):
|
||||||
form.fields[key].widget = HiddenInput()
|
form.fields[key].widget = HiddenInput()
|
||||||
|
|
||||||
return form
|
return form
|
||||||
@@ -83,7 +85,7 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView):
|
|||||||
If not, a 403 error is displayed.
|
If not, a 403 error is displayed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_sample_object(self):
|
def get_sample_object(self): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
return a sample instance of the Model.
|
return a sample instance of the Model.
|
||||||
It should be valid (can be stored properly in database), but must not collide with existing data.
|
It should be valid (can be stored properly in database), but must not collide with existing data.
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from treasury.models import SogeCredit
|
||||||
|
|
||||||
|
|
||||||
class FutureUserTable(tables.Table):
|
class FutureUserTable(tables.Table):
|
||||||
"""
|
"""
|
||||||
@@ -21,6 +23,7 @@ class FutureUserTable(tables.Table):
|
|||||||
fields = ('last_name', 'first_name', 'username', 'email', )
|
fields = ('last_name', 'first_name', 'username', 'email', )
|
||||||
model = User
|
model = User
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': lambda record: 'table-row'
|
||||||
|
+ (' bg-warning' if SogeCredit.objects.filter(user=record).exists() else ''),
|
||||||
'data-href': lambda record: record.pk
|
'data-href': lambda record: record.pk
|
||||||
}
|
}
|
||||||
|
@@ -235,7 +235,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
|||||||
fee += 8000
|
fee += 8000
|
||||||
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
||||||
|
|
||||||
ctx["declare_soge_account"] = True
|
ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
Submodule apps/scripts updated: 7e27c3b71b...dbe7bf6591
@@ -16,10 +16,11 @@ class InvoiceViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/treasury/invoice/
|
then render it on /api/treasury/invoice/
|
||||||
"""
|
"""
|
||||||
queryset = Invoice.objects.order_by("id").all()
|
queryset = Invoice.objects.order_by('id')
|
||||||
serializer_class = InvoiceSerializer
|
serializer_class = InvoiceSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
filterset_fields = ['bde', ]
|
filterset_fields = ['bde', 'object', 'description', 'name', 'address', 'date', 'acquitted', 'locked', ]
|
||||||
|
search_fields = ['$object', '$description', '$name', '$address', ]
|
||||||
|
|
||||||
|
|
||||||
class ProductViewSet(ReadProtectedModelViewSet):
|
class ProductViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -28,10 +29,11 @@ class ProductViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/treasury/product/
|
then render it on /api/treasury/product/
|
||||||
"""
|
"""
|
||||||
queryset = Product.objects.order_by("invoice_id", "id").all()
|
queryset = Product.objects.order_by('invoice_id', 'id')
|
||||||
serializer_class = ProductSerializer
|
serializer_class = ProductSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$designation', ]
|
filterset_fields = ['invoice', 'designation', 'quantity', 'amount', ]
|
||||||
|
search_fields = ['$designation', '$invoice__object', ]
|
||||||
|
|
||||||
|
|
||||||
class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -40,8 +42,11 @@ class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
|
The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
|
||||||
then render it on /api/treasury/remittance_type/
|
then render it on /api/treasury/remittance_type/
|
||||||
"""
|
"""
|
||||||
queryset = RemittanceType.objects.order_by("id")
|
queryset = RemittanceType.objects.order_by('id')
|
||||||
serializer_class = RemittanceTypeSerializer
|
serializer_class = RemittanceTypeSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['note', ]
|
||||||
|
search_fields = ['$note__special_type', ]
|
||||||
|
|
||||||
|
|
||||||
class RemittanceViewSet(ReadProtectedModelViewSet):
|
class RemittanceViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -50,8 +55,11 @@ class RemittanceViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/treasury/remittance/
|
then render it on /api/treasury/remittance/
|
||||||
"""
|
"""
|
||||||
queryset = Remittance.objects.order_by("id")
|
queryset = Remittance.objects.order_by('id')
|
||||||
serializer_class = RemittanceSerializer
|
serializer_class = RemittanceSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'transaction_proxies__transaction', ]
|
||||||
|
search_fields = ['$remittance_type__note__special_type', '$comment', ]
|
||||||
|
|
||||||
|
|
||||||
class SogeCreditViewSet(ReadProtectedModelViewSet):
|
class SogeCreditViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -60,5 +68,10 @@ class SogeCreditViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/treasury/soge_credit/
|
then render it on /api/treasury/soge_credit/
|
||||||
"""
|
"""
|
||||||
queryset = SogeCredit.objects.order_by("id")
|
queryset = SogeCredit.objects.order_by('id')
|
||||||
serializer_class = SogeCreditSerializer
|
serializer_class = SogeCreditSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['user', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name',
|
||||||
|
'user__note__alias__normalized_name', 'transactions', 'credit_transaction', ]
|
||||||
|
search_fields = ['$user__last_name', '$user__first_name', '$user__email', '$user__note__alias__name',
|
||||||
|
'$user__note__alias__normalized_name', ]
|
||||||
|
@@ -28,6 +28,8 @@ class TreasuryConfig(AppConfig):
|
|||||||
source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||||
specialtransactionproxy=None,
|
specialtransactionproxy=None,
|
||||||
):
|
):
|
||||||
SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None)
|
proxy = SpecialTransactionProxy(transaction=transaction, remittance=None)
|
||||||
|
proxy._force_save = True
|
||||||
|
proxy.save()
|
||||||
|
|
||||||
post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy)
|
post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy)
|
||||||
|
@@ -257,6 +257,7 @@ class SpecialTransactionProxy(models.Model):
|
|||||||
Remittance,
|
Remittance,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
null=True,
|
null=True,
|
||||||
|
related_name="transaction_proxies",
|
||||||
verbose_name=_("Remittance"),
|
verbose_name=_("Remittance"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -10,9 +10,8 @@ def save_special_transaction(instance, created, **kwargs):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not hasattr(instance, "_no_signal"):
|
if not hasattr(instance, "_no_signal"):
|
||||||
if instance.is_credit():
|
if created and RemittanceType.objects.filter(
|
||||||
if created and RemittanceType.objects.filter(note=instance.source).exists():
|
note=instance.source if instance.is_credit() else instance.destination).exists():
|
||||||
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
|
proxy = SpecialTransactionProxy(transaction=instance, remittance=None)
|
||||||
else:
|
proxy._force_save = True
|
||||||
if created and RemittanceType.objects.filter(note=instance.destination).exists():
|
proxy.save()
|
||||||
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
|
|
||||||
|
@@ -109,9 +109,6 @@ class SpecialTransactionTable(tables.Table):
|
|||||||
'a': {'class': 'btn btn-primary btn-danger'}
|
'a': {'class': 'btn btn-primary btn-danger'}
|
||||||
}, )
|
}, )
|
||||||
|
|
||||||
def render_id(self, record):
|
|
||||||
return record.specialtransactionproxy.pk
|
|
||||||
|
|
||||||
def render_amount(self, value):
|
def render_amount(self, value):
|
||||||
return pretty_money(value)
|
return pretty_money(value)
|
||||||
|
|
||||||
@@ -147,4 +144,4 @@ class SogeCreditTable(tables.Table):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SogeCredit
|
model = SogeCredit
|
||||||
fields = ('user', 'amount', 'valid', )
|
fields = ('user', 'user__last_name', 'user__first_name', 'amount', 'valid', )
|
||||||
|
@@ -11,8 +11,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-xl-6 text-right">{% trans 'user'|capfirst %}</dt>
|
<dt class="col-xl-6 text-right">{% trans 'last name'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user }}</a></dd>
|
<dd class="col-xl-6">{{ object.user.last_name }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'first name'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.user.first_name }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6 text-right">{% trans 'username'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user.username }}</a></dd>
|
||||||
|
|
||||||
{% if "note.view_note_balance"|has_perm:object.user.note %}
|
{% if "note.view_note_balance"|has_perm:object.user.note %}
|
||||||
<dt class="col-xl-6 text-right">{% trans 'balance'|capfirst %}</dt>
|
<dt class="col-xl-6 text-right">{% trans 'balance'|capfirst %}</dt>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 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 django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@@ -8,7 +9,10 @@ from django.test import TestCase
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from member.models import Membership, Club
|
from member.models import Membership, Club
|
||||||
from note.models import SpecialTransaction, NoteSpecial, Transaction
|
from note.models import SpecialTransaction, NoteSpecial, Transaction
|
||||||
from treasury.models import Invoice, Product, Remittance, RemittanceType, SogeCredit
|
|
||||||
|
from ..api.views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, \
|
||||||
|
SogeCreditViewSet
|
||||||
|
from ..models import Invoice, Product, Remittance, RemittanceType, SogeCredit
|
||||||
|
|
||||||
|
|
||||||
class TestInvoices(TestCase):
|
class TestInvoices(TestCase):
|
||||||
@@ -366,11 +370,8 @@ class TestSogeCredits(TestCase):
|
|||||||
response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)))
|
response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
try:
|
self.assertRaises(ValidationError, self.client.post,
|
||||||
self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True))
|
reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True))
|
||||||
raise AssertionError("It is not possible to delete the soge credit until the note is not credited.")
|
|
||||||
except ValidationError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
SpecialTransaction.objects.create(
|
SpecialTransaction.objects.create(
|
||||||
source=NoteSpecial.objects.get(special_type="Carte bancaire"),
|
source=NoteSpecial.objects.get(special_type="Carte bancaire"),
|
||||||
@@ -399,3 +400,82 @@ class TestSogeCredits(TestCase):
|
|||||||
"""
|
"""
|
||||||
response = self.client.get("/api/treasury/soge_credit/")
|
response = self.client.get("/api/treasury/soge_credit/")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTreasuryAPI(TestAPI):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.invoice = Invoice.objects.create(
|
||||||
|
id=1,
|
||||||
|
object="Object",
|
||||||
|
description="Description",
|
||||||
|
name="Me",
|
||||||
|
address="Earth",
|
||||||
|
acquitted=False,
|
||||||
|
)
|
||||||
|
self.product = Product.objects.create(
|
||||||
|
invoice=self.invoice,
|
||||||
|
designation="Product",
|
||||||
|
quantity=3,
|
||||||
|
amount=3.14,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.credit = SpecialTransaction.objects.create(
|
||||||
|
source=NoteSpecial.objects.get(special_type="Chèque"),
|
||||||
|
destination=self.user.note,
|
||||||
|
amount=4200,
|
||||||
|
reason="Credit",
|
||||||
|
last_name="TOTO",
|
||||||
|
first_name="Toto",
|
||||||
|
bank="Société générale",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.remittance = Remittance.objects.create(
|
||||||
|
remittance_type=RemittanceType.objects.get(),
|
||||||
|
comment="Test remittance",
|
||||||
|
closed=False,
|
||||||
|
)
|
||||||
|
self.credit.specialtransactionproxy.remittance = self.remittance
|
||||||
|
self.credit.specialtransactionproxy.save()
|
||||||
|
|
||||||
|
self.kfet = Club.objects.get(name="Kfet")
|
||||||
|
self.bde = self.kfet.parent_club
|
||||||
|
|
||||||
|
self.kfet_membership = Membership(
|
||||||
|
user=self.user,
|
||||||
|
club=self.kfet,
|
||||||
|
)
|
||||||
|
self.kfet_membership._force_renew_parent = True
|
||||||
|
self.kfet_membership._soge = True
|
||||||
|
self.kfet_membership.save()
|
||||||
|
|
||||||
|
def test_invoice_api(self):
|
||||||
|
"""
|
||||||
|
Load Invoice API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(InvoiceViewSet, "/api/treasury/invoice/")
|
||||||
|
|
||||||
|
def test_product_api(self):
|
||||||
|
"""
|
||||||
|
Load Product API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(ProductViewSet, "/api/treasury/product/")
|
||||||
|
|
||||||
|
def test_remittance_api(self):
|
||||||
|
"""
|
||||||
|
Load Remittance API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(RemittanceViewSet, "/api/treasury/remittance/")
|
||||||
|
|
||||||
|
def test_remittance_type_api(self):
|
||||||
|
"""
|
||||||
|
Load RemittanceType API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(RemittanceTypeViewSet, "/api/treasury/remittance_type/")
|
||||||
|
|
||||||
|
def test_sogecredit_api(self):
|
||||||
|
"""
|
||||||
|
Load SogeCredit API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(SogeCreditViewSet, "/api/treasury/soge_credit/")
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 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
|
||||||
from rest_framework.filters import SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
from api.viewsets import ReadProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
|
|
||||||
from .serializers import WEIClubSerializer, BusSerializer, BusTeamSerializer, WEIRoleSerializer, \
|
from .serializers import WEIClubSerializer, BusSerializer, BusTeamSerializer, WEIRoleSerializer, \
|
||||||
@@ -15,11 +16,14 @@ class WEIClubViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/club/
|
then render it on /api/wei/club/
|
||||||
"""
|
"""
|
||||||
queryset = WEIClub.objects.all()
|
queryset = WEIClub.objects.order_by('id')
|
||||||
serializer_class = WEIClubSerializer
|
serializer_class = WEIClubSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$name', ]
|
filterset_fields = ['name', 'year', 'date_start', 'date_end', 'email', 'note__alias__name',
|
||||||
filterset_fields = ['name', 'year', ]
|
'note__alias__normalized_name', 'parent_club', 'parent_club__name', 'require_memberships',
|
||||||
|
'membership_fee_paid', 'membership_fee_unpaid', 'membership_duration', 'membership_start',
|
||||||
|
'membership_end', ]
|
||||||
|
search_fields = ['$name', '$email', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
class BusViewSet(ReadProtectedModelViewSet):
|
class BusViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -28,11 +32,11 @@ class BusViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/bus/
|
then render it on /api/wei/bus/
|
||||||
"""
|
"""
|
||||||
queryset = Bus.objects
|
queryset = Bus.objects.order_by('id')
|
||||||
serializer_class = BusSerializer
|
serializer_class = BusSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$name', ]
|
filterset_fields = ['name', 'wei', 'description', ]
|
||||||
filterset_fields = ['name', 'wei', ]
|
search_fields = ['$name', '$wei__name', '$description', ]
|
||||||
|
|
||||||
|
|
||||||
class BusTeamViewSet(ReadProtectedModelViewSet):
|
class BusTeamViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -41,11 +45,11 @@ class BusTeamViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/team/
|
then render it on /api/wei/team/
|
||||||
"""
|
"""
|
||||||
queryset = BusTeam.objects
|
queryset = BusTeam.objects.order_by('id')
|
||||||
serializer_class = BusTeamSerializer
|
serializer_class = BusTeamSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$name', ]
|
filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ]
|
||||||
filterset_fields = ['name', 'bus', 'bus__wei', ]
|
search_fields = ['$name', '$bus__name', '$bus__wei__name', '$description', ]
|
||||||
|
|
||||||
|
|
||||||
class WEIRoleViewSet(ReadProtectedModelViewSet):
|
class WEIRoleViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -54,9 +58,10 @@ class WEIRoleViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/role/
|
then render it on /api/wei/role/
|
||||||
"""
|
"""
|
||||||
queryset = WEIRole.objects
|
queryset = WEIRole.objects.order_by('id')
|
||||||
serializer_class = WEIRoleSerializer
|
serializer_class = WEIRoleSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['name', 'permissions', 'memberships', ]
|
||||||
search_fields = ['$name', ]
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
@@ -66,11 +71,17 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/registration/
|
then render it on /api/wei/registration/
|
||||||
"""
|
"""
|
||||||
queryset = WEIRegistration.objects
|
queryset = WEIRegistration.objects.order_by('id')
|
||||||
serializer_class = WEIRegistrationSerializer
|
serializer_class = WEIRegistrationSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
search_fields = ['$user__username', ]
|
filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
|
||||||
filterset_fields = ['user', 'wei', ]
|
'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name',
|
||||||
|
'wei__email', 'wei__year', 'soge_credit', 'caution_check', 'birth_date', 'gender',
|
||||||
|
'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name',
|
||||||
|
'emergency_contact_phone', ]
|
||||||
|
search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email',
|
||||||
|
'$user__note__alias__name', '$user__note__alias__normalized_name', '$wei__name',
|
||||||
|
'$wei__email', '$health_issues', '$emergency_contact_name', '$emergency_contact_phone', ]
|
||||||
|
|
||||||
|
|
||||||
class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
||||||
@@ -79,8 +90,16 @@ class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
|||||||
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
||||||
then render it on /api/wei/membership/
|
then render it on /api/wei/membership/
|
||||||
"""
|
"""
|
||||||
queryset = WEIMembership.objects
|
queryset = WEIMembership.objects.order_by('id')
|
||||||
serializer_class = WEIMembershipSerializer
|
serializer_class = WEIMembershipSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||||
search_fields = ['$user__username', '$bus__name', '$team__name', ]
|
filterset_fields = ['club__name', 'club__email', 'club__note__alias__name',
|
||||||
filterset_fields = ['user', 'club', 'bus', 'team', ]
|
'club__note__alias__normalized_name', 'user__username', 'user__last_name',
|
||||||
|
'user__first_name', 'user__email', 'user__note__alias__name',
|
||||||
|
'user__note__alias__normalized_name', 'date_start', 'date_end', 'fee', 'roles', 'bus',
|
||||||
|
'bus__name', 'team', 'team__name', 'registration', ]
|
||||||
|
ordering_fields = ['id', 'date_start', 'date_end', ]
|
||||||
|
search_fields = ['$club__name', '$club__email', '$club__note__alias__name',
|
||||||
|
'$club__note__alias__normalized_name', '$user__username', '$user__last_name',
|
||||||
|
'$user__first_name', '$user__email', '$user__note__alias__name',
|
||||||
|
'$user__note__alias__normalized_name', '$roles__name', '$bus__name', '$team__name', ]
|
||||||
|
@@ -61,10 +61,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
|
<dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if "note.change_alias"|has_perm:club.note.alias_set.first %}
|
{% if "note.change_alias"|has_perm:club.note.alias.first %}
|
||||||
<dt class="col-xl-4"><a
|
<dt class="col-xl-4"><a
|
||||||
href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||||
<dd class="col-xl-8 text-truncate">{{ club.note.alias_set.all|join:", " }}</dd>
|
<dd class="col-xl-8 text-truncate">{{ club.note.alias.all|join:", " }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt class="col-xl-4">{% trans 'email'|capfirst %}</dt>
|
<dt class="col-xl-4">{% trans 'email'|capfirst %}</dt>
|
||||||
|
@@ -4,16 +4,19 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
|
from api.tests import TestAPI
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from member.models import Membership
|
from member.models import Membership, Club
|
||||||
from note.models import NoteClub, SpecialTransaction
|
from note.models import NoteClub, SpecialTransaction
|
||||||
from treasury.models import SogeCredit
|
from treasury.models import SogeCredit
|
||||||
|
|
||||||
|
from ..api.views import BusViewSet, BusTeamViewSet, WEIClubViewSet, WEIMembershipViewSet, WEIRegistrationViewSet, \
|
||||||
|
WEIRoleViewSet
|
||||||
from ..forms import CurrentSurvey, WEISurveyAlgorithm, WEISurvey
|
from ..forms import CurrentSurvey, WEISurveyAlgorithm, WEISurvey
|
||||||
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
|
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
|
||||||
|
|
||||||
@@ -524,7 +527,7 @@ class TestWEIRegistration(TestCase):
|
|||||||
sess["permission_mask"] = 0
|
sess["permission_mask"] = 0
|
||||||
sess.save()
|
sess.save()
|
||||||
response = self.client.get(reverse("wei:wei_update_registration", kwargs=dict(pk=self.registration.pk)))
|
response = self.client.get(reverse("wei:wei_update_registration", kwargs=dict(pk=self.registration.pk)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 403)
|
||||||
sess["permission_mask"] = 42
|
sess["permission_mask"] = 42
|
||||||
sess.save()
|
sess.save()
|
||||||
|
|
||||||
@@ -807,3 +810,97 @@ class TestWEISurveyAlgorithm(TestCase):
|
|||||||
|
|
||||||
def test_survey_algorithm(self):
|
def test_survey_algorithm(self):
|
||||||
CurrentSurvey.get_algorithm_class()().run_algorithm()
|
CurrentSurvey.get_algorithm_class()().run_algorithm()
|
||||||
|
|
||||||
|
|
||||||
|
class TestWeiAPI(TestAPI):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.year = timezone.now().year
|
||||||
|
self.wei = WEIClub.objects.create(
|
||||||
|
name="Test WEI",
|
||||||
|
email="gc.wei@example.com",
|
||||||
|
parent_club_id=2,
|
||||||
|
membership_fee_paid=12500,
|
||||||
|
membership_fee_unpaid=5500,
|
||||||
|
membership_start=date(self.year, 1, 1),
|
||||||
|
membership_end=date(self.year, 12, 31),
|
||||||
|
membership_duration=396,
|
||||||
|
year=self.year,
|
||||||
|
date_start=date.today() + timedelta(days=2),
|
||||||
|
date_end=date(self.year, 12, 31),
|
||||||
|
)
|
||||||
|
NoteClub.objects.create(club=self.wei)
|
||||||
|
self.bus = Bus.objects.create(
|
||||||
|
name="Test Bus",
|
||||||
|
wei=self.wei,
|
||||||
|
description="Test Bus",
|
||||||
|
)
|
||||||
|
self.team = BusTeam.objects.create(
|
||||||
|
name="Test Team",
|
||||||
|
bus=self.bus,
|
||||||
|
color=0xFFFFFF,
|
||||||
|
description="Test Team",
|
||||||
|
)
|
||||||
|
self.registration = WEIRegistration.objects.create(
|
||||||
|
user_id=self.user.id,
|
||||||
|
wei_id=self.wei.id,
|
||||||
|
soge_credit=True,
|
||||||
|
caution_check=True,
|
||||||
|
birth_date=date(2000, 1, 1),
|
||||||
|
gender="nonbinary",
|
||||||
|
clothing_cut="male",
|
||||||
|
clothing_size="XL",
|
||||||
|
health_issues="I am a bot",
|
||||||
|
emergency_contact_name="Pikachu",
|
||||||
|
emergency_contact_phone="+33123456789",
|
||||||
|
first_year=False,
|
||||||
|
)
|
||||||
|
Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
|
||||||
|
Membership.objects.create(user=self.user, club=Club.objects.get(name="Kfet"))
|
||||||
|
self.membership = WEIMembership.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
club=self.wei,
|
||||||
|
fee=125,
|
||||||
|
bus=self.bus,
|
||||||
|
team=self.team,
|
||||||
|
registration=self.registration,
|
||||||
|
)
|
||||||
|
self.membership.roles.add(WEIRole.objects.last())
|
||||||
|
self.membership.save()
|
||||||
|
|
||||||
|
def test_weiclub_api(self):
|
||||||
|
"""
|
||||||
|
Load WEI API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(WEIClubViewSet, "/api/wei/club/")
|
||||||
|
|
||||||
|
def test_wei_bus_api(self):
|
||||||
|
"""
|
||||||
|
Load Bus API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(BusViewSet, "/api/wei/bus/")
|
||||||
|
|
||||||
|
def test_wei_team_api(self):
|
||||||
|
"""
|
||||||
|
Load BusTeam API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(BusTeamViewSet, "/api/wei/team/")
|
||||||
|
|
||||||
|
def test_weirole_api(self):
|
||||||
|
"""
|
||||||
|
Load WEIRole API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(WEIRoleViewSet, "/api/wei/role/")
|
||||||
|
|
||||||
|
def test_weiregistration_api(self):
|
||||||
|
"""
|
||||||
|
Load WEIRegistration API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(WEIRegistrationViewSet, "/api/wei/registration/")
|
||||||
|
|
||||||
|
def test_weimembership_api(self):
|
||||||
|
"""
|
||||||
|
Load WEIMembership API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(WEIMembershipViewSet, "/api/wei/membership/")
|
||||||
|
@@ -14,6 +14,7 @@ fi
|
|||||||
# Set up Django project
|
# Set up Django project
|
||||||
python3 manage.py collectstatic --noinput
|
python3 manage.py collectstatic --noinput
|
||||||
python3 manage.py compilemessages
|
python3 manage.py compilemessages
|
||||||
|
python3 manage.py compilejsmessages
|
||||||
python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
|
|
||||||
if [ "$1" ]; then
|
if [ "$1" ]; then
|
||||||
|
@@ -7,16 +7,16 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-07 11:42+0200\n"
|
"POT-Creation-Date: 2020-11-15 23:26+0100\n"
|
||||||
"PO-Revision-Date: 2020-09-13 12:39+0200\n"
|
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20/de/>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Poedit 2.3\n"
|
"X-Generator: Weblate 4.3.2\n"
|
||||||
|
|
||||||
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
||||||
#: apps/activity/models.py:167
|
#: apps/activity/models.py:167
|
||||||
@@ -101,7 +101,7 @@ msgstr "Ort"
|
|||||||
|
|
||||||
#: apps/activity/models.py:76
|
#: apps/activity/models.py:76
|
||||||
msgid "Place where the activity is organized, eg. Kfet."
|
msgid "Place where the activity is organized, eg. Kfet."
|
||||||
msgstr "Wo findet die Veranstaltung statt ? (z.B Kfet)"
|
msgstr "Wo findet die Veranstaltung statt ? (z.B Kfet)."
|
||||||
|
|
||||||
#: apps/activity/models.py:83
|
#: apps/activity/models.py:83
|
||||||
#: apps/activity/templates/activity/includes/activity_info.html:22
|
#: apps/activity/templates/activity/includes/activity_info.html:22
|
||||||
@@ -279,11 +279,17 @@ msgstr "Kontostand"
|
|||||||
msgid "Guests list"
|
msgid "Guests list"
|
||||||
msgstr "Gastliste"
|
msgstr "Gastliste"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_detail.html:33
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Guests list"
|
||||||
|
msgid "Guest deleted"
|
||||||
|
msgstr "Gastliste"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_entry.html:14
|
#: apps/activity/templates/activity/activity_entry.html:14
|
||||||
#: apps/note/models/transactions.py:256
|
#: apps/note/models/transactions.py:256
|
||||||
#: apps/note/templates/note/transaction_form.html:16
|
#: apps/note/templates/note/transaction_form.html:16
|
||||||
#: apps/note/templates/note/transaction_form.html:148
|
#: apps/note/templates/note/transaction_form.html:148
|
||||||
#: note_kfet/templates/base.html:70
|
#: note_kfet/templates/base.html:73
|
||||||
msgid "Transfer"
|
msgid "Transfer"
|
||||||
msgstr "Überweisen"
|
msgstr "Überweisen"
|
||||||
|
|
||||||
@@ -308,6 +314,17 @@ msgstr "Eintritte"
|
|||||||
msgid "Return to activity page"
|
msgid "Return to activity page"
|
||||||
msgstr "Zurück zur Veranstaltungseite"
|
msgstr "Zurück zur Veranstaltungseite"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:89
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:124
|
||||||
|
msgid "Entry done, but caution: the user is not a Kfet member."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:127
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Entry page"
|
||||||
|
msgid "Entry done!"
|
||||||
|
msgstr "Eintrittseite"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_form.html:16
|
#: apps/activity/templates/activity/activity_form.html:16
|
||||||
#: apps/member/templates/member/add_members.html:46
|
#: apps/member/templates/member/add_members.html:46
|
||||||
#: apps/member/templates/member/club_form.html:16
|
#: apps/member/templates/member/club_form.html:16
|
||||||
@@ -359,11 +376,11 @@ msgstr "Schlusss"
|
|||||||
|
|
||||||
#: apps/activity/templates/activity/includes/activity_info.html:68
|
#: apps/activity/templates/activity/includes/activity_info.html:68
|
||||||
msgid "invalidate"
|
msgid "invalidate"
|
||||||
msgstr "invalidate"
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/templates/activity/includes/activity_info.html:68
|
#: apps/activity/templates/activity/includes/activity_info.html:68
|
||||||
msgid "validate"
|
msgid "validate"
|
||||||
msgstr "validate"
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||||
#: apps/logs/models.py:64 apps/note/tables.py:195
|
#: apps/logs/models.py:64 apps/note/tables.py:195
|
||||||
@@ -378,7 +395,7 @@ msgstr "Einladen"
|
|||||||
msgid "Create new activity"
|
msgid "Create new activity"
|
||||||
msgstr "Neue Veranstaltung schaffen"
|
msgstr "Neue Veranstaltung schaffen"
|
||||||
|
|
||||||
#: apps/activity/views.py:67 note_kfet/templates/base.html:88
|
#: apps/activity/views.py:67 note_kfet/templates/base.html:91
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr "Veranstaltungen"
|
msgstr "Veranstaltungen"
|
||||||
|
|
||||||
@@ -1620,7 +1637,7 @@ msgstr "Tatsen finden"
|
|||||||
msgid "Update button"
|
msgid "Update button"
|
||||||
msgstr "Tatse bearbeiten"
|
msgstr "Tatse bearbeiten"
|
||||||
|
|
||||||
#: apps/note/views.py:151 note_kfet/templates/base.html:64
|
#: apps/note/views.py:151 note_kfet/templates/base.html:67
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr "Verbräuche"
|
msgstr "Verbräuche"
|
||||||
|
|
||||||
@@ -1798,7 +1815,7 @@ msgstr ""
|
|||||||
"diesen Parametern zu erstellen. Bitte korrigieren Sie Ihre Daten und "
|
"diesen Parametern zu erstellen. Bitte korrigieren Sie Ihre Daten und "
|
||||||
"versuchen Sie es erneut."
|
"versuchen Sie es erneut."
|
||||||
|
|
||||||
#: apps/permission/views.py:110 note_kfet/templates/base.html:106
|
#: apps/permission/views.py:110 note_kfet/templates/base.html:109
|
||||||
msgid "Rights"
|
msgid "Rights"
|
||||||
msgstr "Rechten"
|
msgstr "Rechten"
|
||||||
|
|
||||||
@@ -2007,7 +2024,7 @@ msgstr ""
|
|||||||
msgid "Invalidate pre-registration"
|
msgid "Invalidate pre-registration"
|
||||||
msgstr "Ungültige Vorregistrierung"
|
msgstr "Ungültige Vorregistrierung"
|
||||||
|
|
||||||
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:94
|
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:97
|
||||||
msgid "Treasury"
|
msgid "Treasury"
|
||||||
msgstr "Quaestor"
|
msgstr "Quaestor"
|
||||||
|
|
||||||
@@ -2409,7 +2426,7 @@ msgstr "Krediten von der Société générale handeln"
|
|||||||
|
|
||||||
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
||||||
#: apps/wei/models.py:61 apps/wei/models.py:167
|
#: apps/wei/models.py:61 apps/wei/models.py:167
|
||||||
#: note_kfet/templates/base.html:100
|
#: note_kfet/templates/base.html:103
|
||||||
msgid "WEI"
|
msgid "WEI"
|
||||||
msgstr "WEI"
|
msgstr "WEI"
|
||||||
|
|
||||||
@@ -3021,34 +3038,34 @@ msgstr "Reset"
|
|||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "Die BDE ENS-Paris-Saclay Note."
|
msgstr "Die BDE ENS-Paris-Saclay Note."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:76
|
#: note_kfet/templates/base.html:79
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Users"
|
msgstr "Users"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:82
|
#: note_kfet/templates/base.html:85
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr "Clubs"
|
msgstr "Clubs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:111
|
#: note_kfet/templates/base.html:114
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr "Admin"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:125
|
#: note_kfet/templates/base.html:128
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "Mein Konto"
|
msgstr "Mein Konto"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:128
|
#: note_kfet/templates/base.html:131
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Abmelden"
|
msgstr "Abmelden"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:136
|
#: note_kfet/templates/base.html:139
|
||||||
#: note_kfet/templates/registration/signup.html:6
|
#: note_kfet/templates/registration/signup.html:6
|
||||||
#: note_kfet/templates/registration/signup.html:11
|
#: note_kfet/templates/registration/signup.html:11
|
||||||
#: note_kfet/templates/registration/signup.html:28
|
#: note_kfet/templates/registration/signup.html:28
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Registrieren"
|
msgstr "Registrieren"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:143
|
#: note_kfet/templates/base.html:146
|
||||||
#: note_kfet/templates/registration/login.html:6
|
#: note_kfet/templates/registration/login.html:6
|
||||||
#: note_kfet/templates/registration/login.html:15
|
#: note_kfet/templates/registration/login.html:15
|
||||||
#: note_kfet/templates/registration/login.html:38
|
#: note_kfet/templates/registration/login.html:38
|
||||||
|
133
locale/de/LC_MESSAGES/djangojs.po
Normal file
133
locale/de/LC_MESSAGES/djangojs.po
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||||
|
"PO-Revision-Date: 2020-11-16 20:21+0000\n"
|
||||||
|
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||||
|
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>"
|
||||||
|
"\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
|
"X-Generator: Weblate 4.3.2\n"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:17
|
||||||
|
msgid "Alias successfully added"
|
||||||
|
msgstr "Alias erfolgreich hinzugefügt"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:33
|
||||||
|
msgid "Alias successfully deleted"
|
||||||
|
msgstr "Alias erfolgreich gelöscht"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:225
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Warnung, die Transaktion aus der Note %s gelingt, aber die Emitternote %s "
|
||||||
|
"ist sehr negativ."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:228
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Warnung, die Transaktion aus der Note %s gelingt, aber die Emitternote %s "
|
||||||
|
"ist negativ."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:232
|
||||||
|
#: apps/note/static/note/js/transfer.js:298
|
||||||
|
#: apps/note/static/note/js/transfer.js:401
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||||
|
msgstr "Warnung, der Emittent Hinweis %s ist kein BDE-Mitglied mehr."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:253
|
||||||
|
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||||
|
msgstr ""
|
||||||
|
"Die Transaktion konnte aufgrund eines unzureichenden Saldos nicht validiert "
|
||||||
|
"werden."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:238
|
||||||
|
msgid "This field is required and must contain a decimal positive number."
|
||||||
|
msgstr ""
|
||||||
|
"Dieses Feld ist erforderlich und muss eine positive Dezimalzahl enthalten."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:245
|
||||||
|
msgid "The amount must stay under 21,474,836.47 €."
|
||||||
|
msgstr "Der Betrag muss unter 21.474.836,47 € bleiben."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:251
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Dies ist ein Pflichtfeld."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:277
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||||
|
"same source and destination note."
|
||||||
|
msgstr ""
|
||||||
|
"Warnung: Die Transaktion von %s von %s nach %s wurde nicht durchgeführt, da "
|
||||||
|
"es sich um die gleiche Quell- und Zielnotiz handelt."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:301
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the destination note %s is no more a BDE member."
|
||||||
|
msgstr "Warnung, der Bestimmungsvermerk %s ist kein BDE-Mitglied mehr."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:307
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||||
|
"die Emitternote %s ist sehr negativ."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:312
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||||
|
"die Emitternote %s ist negativ."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:318
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s succeed!"
|
||||||
|
msgstr "Übertragung von %s von %s auf %s gelingt!"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:325
|
||||||
|
#: apps/note/static/note/js/transfer.js:346
|
||||||
|
#: apps/note/static/note/js/transfer.js:353
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s failed: %s"
|
||||||
|
msgstr "Übertragung von %s von %s auf %s fehlgeschlagen: %s"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:347
|
||||||
|
msgid "insufficient funds"
|
||||||
|
msgstr "unzureichende Geldmittel"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:400
|
||||||
|
msgid "Credit/debit succeed!"
|
||||||
|
msgstr "Kredit/Debit erfolgreich!"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:407
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Credit/debit failed: %s"
|
||||||
|
msgstr "Kredit/Debit fehlgeschlagen: %s"
|
||||||
|
|
||||||
|
#: note_kfet/static/js/base.js:366
|
||||||
|
msgid "An error occured while (in)validating this transaction:"
|
||||||
|
msgstr "Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
|
@@ -7,8 +7,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-07 11:42+0200\n"
|
"POT-Creation-Date: 2020-11-15 23:26+0100\n"
|
||||||
"PO-Revision-Date: 2020-09-19 14:56+0200\n"
|
"PO-Revision-Date: 2020-11-17 23:47+0100\n"
|
||||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
@@ -278,11 +278,15 @@ msgstr "Saldo de la cuenta"
|
|||||||
msgid "Guests list"
|
msgid "Guests list"
|
||||||
msgstr "Lista de los invitados"
|
msgstr "Lista de los invitados"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_detail.html:33
|
||||||
|
msgid "Guest deleted"
|
||||||
|
msgstr "Invitados suprimidos"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_entry.html:14
|
#: apps/activity/templates/activity/activity_entry.html:14
|
||||||
#: apps/note/models/transactions.py:256
|
#: apps/note/models/transactions.py:256
|
||||||
#: apps/note/templates/note/transaction_form.html:16
|
#: apps/note/templates/note/transaction_form.html:16
|
||||||
#: apps/note/templates/note/transaction_form.html:148
|
#: apps/note/templates/note/transaction_form.html:148
|
||||||
#: note_kfet/templates/base.html:70
|
#: note_kfet/templates/base.html:73
|
||||||
msgid "Transfer"
|
msgid "Transfer"
|
||||||
msgstr "Transferencia"
|
msgstr "Transferencia"
|
||||||
|
|
||||||
@@ -307,6 +311,15 @@ msgstr "Entradas"
|
|||||||
msgid "Return to activity page"
|
msgid "Return to activity page"
|
||||||
msgstr "Regresar a la página de la actividad"
|
msgstr "Regresar a la página de la actividad"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:89
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:124
|
||||||
|
msgid "Entry done, but caution: the user is not a Kfet member."
|
||||||
|
msgstr "Entrada echa, pero cuidado : el usuario no es un miembro de la Kfet."
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:127
|
||||||
|
msgid "Entry done!"
|
||||||
|
msgstr "Entrada echa !"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_form.html:16
|
#: apps/activity/templates/activity/activity_form.html:16
|
||||||
#: apps/member/templates/member/add_members.html:46
|
#: apps/member/templates/member/add_members.html:46
|
||||||
#: apps/member/templates/member/club_form.html:16
|
#: apps/member/templates/member/club_form.html:16
|
||||||
@@ -377,7 +390,7 @@ msgstr "Invitar"
|
|||||||
msgid "Create new activity"
|
msgid "Create new activity"
|
||||||
msgstr "Crear una nueva actividad"
|
msgstr "Crear una nueva actividad"
|
||||||
|
|
||||||
#: apps/activity/views.py:67 note_kfet/templates/base.html:88
|
#: apps/activity/views.py:67 note_kfet/templates/base.html:91
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr "Actividades"
|
msgstr "Actividades"
|
||||||
|
|
||||||
@@ -586,7 +599,7 @@ msgstr "sección"
|
|||||||
|
|
||||||
#: apps/member/models.py:46
|
#: apps/member/models.py:46
|
||||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgstr "i.e. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
|
|
||||||
#: apps/member/models.py:54 apps/wei/templates/wei/weimembership_form.html:32
|
#: apps/member/models.py:54 apps/wei/templates/wei/weimembership_form.html:32
|
||||||
msgid "department"
|
msgid "department"
|
||||||
@@ -1617,7 +1630,7 @@ msgstr "Buscar un botón"
|
|||||||
msgid "Update button"
|
msgid "Update button"
|
||||||
msgstr "Modificar el botón"
|
msgstr "Modificar el botón"
|
||||||
|
|
||||||
#: apps/note/views.py:151 note_kfet/templates/base.html:64
|
#: apps/note/views.py:151 note_kfet/templates/base.html:67
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr "Consumiciones"
|
msgstr "Consumiciones"
|
||||||
|
|
||||||
@@ -1793,7 +1806,7 @@ msgid ""
|
|||||||
"with these parameters. Please correct your data and retry."
|
"with these parameters. Please correct your data and retry."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/permission/views.py:110 note_kfet/templates/base.html:106
|
#: apps/permission/views.py:110 note_kfet/templates/base.html:109
|
||||||
msgid "Rights"
|
msgid "Rights"
|
||||||
msgstr "Permisos"
|
msgstr "Permisos"
|
||||||
|
|
||||||
@@ -1810,18 +1823,20 @@ msgid "This email address is already used."
|
|||||||
msgstr "Este correo electrónico ya esta utilizado."
|
msgstr "Este correo electrónico ya esta utilizado."
|
||||||
|
|
||||||
#: apps/registration/forms.py:49
|
#: apps/registration/forms.py:49
|
||||||
#, fuzzy
|
|
||||||
#| msgid "You already opened an account in the Société générale."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"I declare that I opened a bank account in the Société générale with the BDE "
|
"I declare that I opened a bank account in the Société générale with the BDE "
|
||||||
"partnership."
|
"partnership."
|
||||||
msgstr "Usted ya abrió una cuenta a la Société Générale."
|
msgstr ""
|
||||||
|
"Declaro que ya abrió una cuenta a la Société Générale en colaboración con el "
|
||||||
|
"BDE."
|
||||||
|
|
||||||
#: apps/registration/forms.py:50
|
#: apps/registration/forms.py:50
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: this engages you to open your bank account. If you finally decides "
|
"Warning: this engages you to open your bank account. If you finally decides "
|
||||||
"to don't open your account, you will have to pay the BDE membership."
|
"to don't open your account, you will have to pay the BDE membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Cuidado : esto le obliga abrir su cuenta bancaria. Si cambia de idea y no "
|
||||||
|
"abre su cuenta bancaria, tendrá que pagar su afiliación al BDE."
|
||||||
|
|
||||||
#: apps/registration/forms.py:58
|
#: apps/registration/forms.py:58
|
||||||
msgid "Register to the WEI"
|
msgid "Register to the WEI"
|
||||||
@@ -1892,11 +1907,9 @@ msgid "Validate account"
|
|||||||
msgstr "Validar la cuenta"
|
msgstr "Validar la cuenta"
|
||||||
|
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:62
|
#: apps/registration/templates/registration/future_profile_detail.html:62
|
||||||
#, fuzzy
|
|
||||||
#| msgid "You already opened an account in the Société générale."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"The user declared that he/she opened a bank account in the Société générale."
|
"The user declared that he/she opened a bank account in the Société générale."
|
||||||
msgstr "Usted ya abrió una cuenta a la Société Générale."
|
msgstr "El usuario declara que ya abrió una cuenta a la Société Générale."
|
||||||
|
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:71
|
#: apps/registration/templates/registration/future_profile_detail.html:71
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:127
|
#: apps/wei/templates/wei/weimembership_form.html:127
|
||||||
@@ -2001,7 +2014,7 @@ msgstr ""
|
|||||||
msgid "Invalidate pre-registration"
|
msgid "Invalidate pre-registration"
|
||||||
msgstr "Invalidar la afiliación"
|
msgstr "Invalidar la afiliación"
|
||||||
|
|
||||||
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:94
|
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:97
|
||||||
msgid "Treasury"
|
msgid "Treasury"
|
||||||
msgstr "Tesorería"
|
msgstr "Tesorería"
|
||||||
|
|
||||||
@@ -2398,7 +2411,7 @@ msgstr "Gestionar los créditos de la Société Générale"
|
|||||||
|
|
||||||
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
||||||
#: apps/wei/models.py:61 apps/wei/models.py:167
|
#: apps/wei/models.py:61 apps/wei/models.py:167
|
||||||
#: note_kfet/templates/base.html:100
|
#: note_kfet/templates/base.html:103
|
||||||
msgid "WEI"
|
msgid "WEI"
|
||||||
msgstr "WEI"
|
msgstr "WEI"
|
||||||
|
|
||||||
@@ -2997,40 +3010,40 @@ msgstr ""
|
|||||||
|
|
||||||
#: note_kfet/templates/autocomplete_model.html:14
|
#: note_kfet/templates/autocomplete_model.html:14
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr ""
|
msgstr "Reiniciar"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:14
|
#: note_kfet/templates/base.html:14
|
||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "La note del BDE de la ENS Paris-Saclay."
|
msgstr "La note del BDE de la ENS Paris-Saclay."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:76
|
#: note_kfet/templates/base.html:79
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Usuarios"
|
msgstr "Usuarios"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:82
|
#: note_kfet/templates/base.html:85
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr "Clubs"
|
msgstr "Clubs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:111
|
#: note_kfet/templates/base.html:114
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:125
|
#: note_kfet/templates/base.html:128
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "Mi cuenta"
|
msgstr "Mi cuenta"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:128
|
#: note_kfet/templates/base.html:131
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Desconectarse"
|
msgstr "Desconectarse"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:136
|
#: note_kfet/templates/base.html:139
|
||||||
#: note_kfet/templates/registration/signup.html:6
|
#: note_kfet/templates/registration/signup.html:6
|
||||||
#: note_kfet/templates/registration/signup.html:11
|
#: note_kfet/templates/registration/signup.html:11
|
||||||
#: note_kfet/templates/registration/signup.html:28
|
#: note_kfet/templates/registration/signup.html:28
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Registrar"
|
msgstr "Registrar"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:143
|
#: note_kfet/templates/base.html:146
|
||||||
#: note_kfet/templates/registration/login.html:6
|
#: note_kfet/templates/registration/login.html:6
|
||||||
#: note_kfet/templates/registration/login.html:15
|
#: note_kfet/templates/registration/login.html:15
|
||||||
#: note_kfet/templates/registration/login.html:38
|
#: note_kfet/templates/registration/login.html:38
|
||||||
@@ -3043,10 +3056,12 @@ msgid ""
|
|||||||
"You are not a BDE member anymore. Please renew your membership if you want "
|
"You are not a BDE member anymore. Please renew your membership if you want "
|
||||||
"to use the note."
|
"to use the note."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Usted ya no está miembro del BDE. Por favor renueva su afiliación si quiere "
|
||||||
|
"usar la note."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:160
|
#: note_kfet/templates/base.html:160
|
||||||
msgid "You are not a Kfet member, so you can't use your note account."
|
msgid "You are not a Kfet member, so you can't use your note account."
|
||||||
msgstr ""
|
msgstr "Usted no es un miembro de la Kfet, no puede usar su cuenta note."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:166
|
#: note_kfet/templates/base.html:166
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -3064,6 +3079,10 @@ msgid ""
|
|||||||
"yet. This verification procedure may last a few days. Please make sure that "
|
"yet. This verification procedure may last a few days. Please make sure that "
|
||||||
"you go to the end of the account creation."
|
"you go to the end of the account creation."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Usted declaró que abrió una cuenta bancaria a la Société Générale. El banco "
|
||||||
|
"no convalidó la cuenta al BDE, así que el bonus de 80€ no fue dado y la "
|
||||||
|
"afiliación no está pagada. El proceso de convalidación puede durar unos "
|
||||||
|
"días. Por favor comprueba que fue hasta el final de la creación de la cuenta."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:194
|
#: note_kfet/templates/base.html:194
|
||||||
msgid "Contact us"
|
msgid "Contact us"
|
||||||
@@ -3188,7 +3207,6 @@ msgstr ""
|
|||||||
#~ msgid "Central Authentication Service"
|
#~ msgid "Central Authentication Service"
|
||||||
#~ msgstr "Servicio Central de Autentificación"
|
#~ msgstr "Servicio Central de Autentificación"
|
||||||
|
|
||||||
#, python-format
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "A new version of the application is available. This instance runs "
|
#~ "A new version of the application is available. This instance runs "
|
||||||
#~ "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
|
#~ "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
|
||||||
|
129
locale/es/LC_MESSAGES/djangojs.po
Normal file
129
locale/es/LC_MESSAGES/djangojs.po
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||||
|
"PO-Revision-Date: 2020-11-21 12:23+0100\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:17
|
||||||
|
msgid "Alias successfully added"
|
||||||
|
msgstr "Alias añadido con éxito"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:33
|
||||||
|
msgid "Alias successfully deleted"
|
||||||
|
msgstr "Alias suprimido con éxito"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:225
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Cuidado, la transacción de %s fue un éxito, pero la note %s está muy "
|
||||||
|
"negativa."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:228
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Cuidado, la transacción de %s fue un éxito, pero la note %s está negativa."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:232
|
||||||
|
#: apps/note/static/note/js/transfer.js:298
|
||||||
|
#: apps/note/static/note/js/transfer.js:401
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||||
|
msgstr "Cuidado, la note remitente %s no está más miembro del BDE."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:253
|
||||||
|
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||||
|
msgstr ""
|
||||||
|
"La transacción no pudo ser validada por culpa de saldo demasiado bajo."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:238
|
||||||
|
msgid "This field is required and must contain a decimal positive number."
|
||||||
|
msgstr "Este campo obligatorio requiere un número decimal positivo."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:245
|
||||||
|
msgid "The amount must stay under 21,474,836.47 €."
|
||||||
|
msgstr "El monto no puede superar los 21 474 836,47 €."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:251
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Este campo es obligatorio."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:277
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||||
|
"same source and destination note."
|
||||||
|
msgstr ""
|
||||||
|
"Cuidado : la transacción de %s de %s a %s no fue echa porque la fuente y el "
|
||||||
|
"destino son iguales."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:301
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the destination note %s is no more a BDE member."
|
||||||
|
msgstr "Cuidado, la note destino %s no está más miembro del BDE."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:307
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||||
|
"la note fuente %s está muy negativa."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:312
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||||
|
"la note fuente %s está negativa."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:318
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s succeed!"
|
||||||
|
msgstr "¡ La transacción de %s de %s a %s fue un éxito !"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:325
|
||||||
|
#: apps/note/static/note/js/transfer.js:346
|
||||||
|
#: apps/note/static/note/js/transfer.js:353
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s failed: %s"
|
||||||
|
msgstr "La transacción de %s de %s a %s fue un fracaso : %s"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:347
|
||||||
|
msgid "insufficient funds"
|
||||||
|
msgstr "fundos insuficientes"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:400
|
||||||
|
msgid "Credit/debit succeed!"
|
||||||
|
msgstr "¡ Crédito/débito tubo éxito !"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:407
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Credit/debit failed: %s"
|
||||||
|
msgstr "Crédito/débito falló : %s"
|
||||||
|
|
||||||
|
#: note_kfet/static/js/base.js:366
|
||||||
|
msgid "An error occured while (in)validating this transaction:"
|
||||||
|
msgstr "Un error ocurrió durante la (in)validación de esta transacción :"
|
@@ -7,16 +7,16 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-07 11:42+0200\n"
|
"POT-Creation-Date: 2020-11-15 23:26+0100\n"
|
||||||
"PO-Revision-Date: 2020-09-13 12:36+0200\n"
|
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Poedit 2.3\n"
|
"X-Generator: Weblate 4.3.2\n"
|
||||||
|
|
||||||
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
||||||
#: apps/activity/models.py:167
|
#: apps/activity/models.py:167
|
||||||
@@ -279,11 +279,15 @@ msgstr "Solde du compte"
|
|||||||
msgid "Guests list"
|
msgid "Guests list"
|
||||||
msgstr "Liste des invités"
|
msgstr "Liste des invités"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_detail.html:33
|
||||||
|
msgid "Guest deleted"
|
||||||
|
msgstr "Invité supprimé"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_entry.html:14
|
#: apps/activity/templates/activity/activity_entry.html:14
|
||||||
#: apps/note/models/transactions.py:256
|
#: apps/note/models/transactions.py:256
|
||||||
#: apps/note/templates/note/transaction_form.html:16
|
#: apps/note/templates/note/transaction_form.html:16
|
||||||
#: apps/note/templates/note/transaction_form.html:148
|
#: apps/note/templates/note/transaction_form.html:148
|
||||||
#: note_kfet/templates/base.html:70
|
#: note_kfet/templates/base.html:73
|
||||||
msgid "Transfer"
|
msgid "Transfer"
|
||||||
msgstr "Virement"
|
msgstr "Virement"
|
||||||
|
|
||||||
@@ -308,6 +312,16 @@ msgstr "Entrées"
|
|||||||
msgid "Return to activity page"
|
msgid "Return to activity page"
|
||||||
msgstr "Retour à la page de l'activité"
|
msgstr "Retour à la page de l'activité"
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:89
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:124
|
||||||
|
msgid "Entry done, but caution: the user is not a Kfet member."
|
||||||
|
msgstr ""
|
||||||
|
"Entrée effectuée, mais attention : la personne n'est pas un adhérent Kfet."
|
||||||
|
|
||||||
|
#: apps/activity/templates/activity/activity_entry.html:127
|
||||||
|
msgid "Entry done!"
|
||||||
|
msgstr "Entrée effectuée !"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_form.html:16
|
#: apps/activity/templates/activity/activity_form.html:16
|
||||||
#: apps/member/templates/member/add_members.html:46
|
#: apps/member/templates/member/add_members.html:46
|
||||||
#: apps/member/templates/member/club_form.html:16
|
#: apps/member/templates/member/club_form.html:16
|
||||||
@@ -378,7 +392,7 @@ msgstr "Inviter"
|
|||||||
msgid "Create new activity"
|
msgid "Create new activity"
|
||||||
msgstr "Créer une nouvelle activité"
|
msgstr "Créer une nouvelle activité"
|
||||||
|
|
||||||
#: apps/activity/views.py:67 note_kfet/templates/base.html:88
|
#: apps/activity/views.py:67 note_kfet/templates/base.html:91
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr "Activités"
|
msgstr "Activités"
|
||||||
|
|
||||||
@@ -1622,7 +1636,7 @@ msgstr "Chercher un bouton"
|
|||||||
msgid "Update button"
|
msgid "Update button"
|
||||||
msgstr "Modifier le bouton"
|
msgstr "Modifier le bouton"
|
||||||
|
|
||||||
#: apps/note/views.py:151 note_kfet/templates/base.html:64
|
#: apps/note/views.py:151 note_kfet/templates/base.html:67
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr "Consommations"
|
msgstr "Consommations"
|
||||||
|
|
||||||
@@ -1637,12 +1651,12 @@ msgstr "Rechercher des transactions"
|
|||||||
#: apps/permission/models.py:92
|
#: apps/permission/models.py:92
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Can {type} {model}.{field} in {query}"
|
msgid "Can {type} {model}.{field} in {query}"
|
||||||
msgstr "Can {type} {model}.{field} in {query}"
|
msgstr "Peut {type} {model}.{field} si {query}"
|
||||||
|
|
||||||
#: apps/permission/models.py:94
|
#: apps/permission/models.py:94
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Can {type} {model} in {query}"
|
msgid "Can {type} {model} in {query}"
|
||||||
msgstr "Can {type} {model} in {query}"
|
msgstr "Peut {type} {model} si {query}"
|
||||||
|
|
||||||
#: apps/permission/models.py:107
|
#: apps/permission/models.py:107
|
||||||
msgid "rank"
|
msgid "rank"
|
||||||
@@ -1801,7 +1815,7 @@ msgstr ""
|
|||||||
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
|
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
|
||||||
"avec ces paramètres. Merci de les corriger et de réessayer."
|
"avec ces paramètres. Merci de les corriger et de réessayer."
|
||||||
|
|
||||||
#: apps/permission/views.py:110 note_kfet/templates/base.html:106
|
#: apps/permission/views.py:110 note_kfet/templates/base.html:109
|
||||||
msgid "Rights"
|
msgid "Rights"
|
||||||
msgstr "Droits"
|
msgstr "Droits"
|
||||||
|
|
||||||
@@ -2008,7 +2022,7 @@ msgstr ""
|
|||||||
msgid "Invalidate pre-registration"
|
msgid "Invalidate pre-registration"
|
||||||
msgstr "Invalider l'inscription"
|
msgstr "Invalider l'inscription"
|
||||||
|
|
||||||
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:94
|
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:97
|
||||||
msgid "Treasury"
|
msgid "Treasury"
|
||||||
msgstr "Trésorerie"
|
msgstr "Trésorerie"
|
||||||
|
|
||||||
@@ -2408,7 +2422,7 @@ msgstr "Gérer les crédits de la Société générale"
|
|||||||
|
|
||||||
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
|
||||||
#: apps/wei/models.py:61 apps/wei/models.py:167
|
#: apps/wei/models.py:61 apps/wei/models.py:167
|
||||||
#: note_kfet/templates/base.html:100
|
#: note_kfet/templates/base.html:103
|
||||||
msgid "WEI"
|
msgid "WEI"
|
||||||
msgstr "WEI"
|
msgstr "WEI"
|
||||||
|
|
||||||
@@ -2786,10 +2800,9 @@ msgid ""
|
|||||||
"validated the creation of the account, or to change the payment method."
|
"validated the creation of the account, or to change the payment method."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Le WEI va être payé par la Société générale. L'adhésion sera créée même si "
|
"Le WEI va être payé par la Société générale. L'adhésion sera créée même si "
|
||||||
"la banque n'a pas encore payé le BDE.\n"
|
"la banque n'a pas encore payé le BDE. La transaction d'adhésion sera créée "
|
||||||
"La transaction d'adhésion sera créée mais invalide. Vous devrez la valider "
|
"mais invalide. Vous devrez la valider une fois que la banque aura validé la "
|
||||||
"une fois que la banque\n"
|
"création du compte, ou bien changer de moyen de paiement."
|
||||||
"aura validé la création du compte, ou bien changer de moyen de paiement."
|
|
||||||
|
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:149
|
#: apps/wei/templates/wei/weimembership_form.html:149
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -2806,7 +2819,7 @@ msgid ""
|
|||||||
"The note has enough money (%(pretty_fee)s required), the registration is "
|
"The note has enough money (%(pretty_fee)s required), the registration is "
|
||||||
"possible."
|
"possible."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La note a assez d'argent (%(pretty_fee) requis), l'inscription est possible."
|
"La note a assez d'argent (%(pretty_fee)s requis), l'inscription est possible."
|
||||||
|
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:166
|
#: apps/wei/templates/wei/weimembership_form.html:166
|
||||||
msgid "The user didn't give her/his caution check."
|
msgid "The user didn't give her/his caution check."
|
||||||
@@ -3020,34 +3033,34 @@ msgstr "Réinitialiser"
|
|||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:76
|
#: note_kfet/templates/base.html:79
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilisateurs"
|
msgstr "Utilisateurs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:82
|
#: note_kfet/templates/base.html:85
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr "Clubs"
|
msgstr "Clubs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:111
|
#: note_kfet/templates/base.html:114
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr "Admin"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:125
|
#: note_kfet/templates/base.html:128
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "Mon compte"
|
msgstr "Mon compte"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:128
|
#: note_kfet/templates/base.html:131
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Se déconnecter"
|
msgstr "Se déconnecter"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:136
|
#: note_kfet/templates/base.html:139
|
||||||
#: note_kfet/templates/registration/signup.html:6
|
#: note_kfet/templates/registration/signup.html:6
|
||||||
#: note_kfet/templates/registration/signup.html:11
|
#: note_kfet/templates/registration/signup.html:11
|
||||||
#: note_kfet/templates/registration/signup.html:28
|
#: note_kfet/templates/registration/signup.html:28
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Inscription"
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:143
|
#: note_kfet/templates/base.html:146
|
||||||
#: note_kfet/templates/registration/login.html:6
|
#: note_kfet/templates/registration/login.html:6
|
||||||
#: note_kfet/templates/registration/login.html:15
|
#: note_kfet/templates/registration/login.html:15
|
||||||
#: note_kfet/templates/registration/login.html:38
|
#: note_kfet/templates/registration/login.html:38
|
||||||
|
134
locale/fr/LC_MESSAGES/djangojs.po
Normal file
134
locale/fr/LC_MESSAGES/djangojs.po
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:17
|
||||||
|
msgid "Alias successfully added"
|
||||||
|
msgstr "Alias ajouté avec succès"
|
||||||
|
|
||||||
|
#: apps/member/static/member/js/alias.js:33
|
||||||
|
msgid "Alias successfully deleted"
|
||||||
|
msgstr "Alias supprimé avec succès"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:225
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Attention, La transaction depuis la note %s a été réalisée avec succès, mais "
|
||||||
|
"la note émettrice %s est en négatif sévère."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:228
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction from the note %s succeed, but the emitter note %s "
|
||||||
|
"is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Attention, La transaction depuis la note %s a été réalisée avec succès, mais "
|
||||||
|
"la note émettrice %s est en négatif."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:232
|
||||||
|
#: apps/note/static/note/js/transfer.js:298
|
||||||
|
#: apps/note/static/note/js/transfer.js:401
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||||
|
msgstr "Attention, la note émettrice %s n'est plus adhérente."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/consos.js:253
|
||||||
|
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||||
|
msgstr ""
|
||||||
|
"La transaction n'a pas pu être validée pour cause de solde insuffisant."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:238
|
||||||
|
msgid "This field is required and must contain a decimal positive number."
|
||||||
|
msgstr ""
|
||||||
|
"Ce champ est requis et doit comporter un nombre décimal strictement positif."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:245
|
||||||
|
msgid "The amount must stay under 21,474,836.47 €."
|
||||||
|
msgstr "Le montant ne doit pas excéder 21 474 836.47 €."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:251
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Ce champ est requis."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:277
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||||
|
"same source and destination note."
|
||||||
|
msgstr ""
|
||||||
|
"Attention : la transaction de %s de la note %s vers la note %s n'a pas été "
|
||||||
|
"faite car il s'agit de la même note au départ et à l'arrivée."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:301
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Warning, the destination note %s is no more a BDE member."
|
||||||
|
msgstr "Attention, la note de destination %s n'est plus adhérente."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:307
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is very negative."
|
||||||
|
msgstr ""
|
||||||
|
"Attention, La transaction de %s depuis la note %s vers la note %s a été "
|
||||||
|
"réalisée avec succès, mais la note émettrice %s est en négatif sévère."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:312
|
||||||
|
#, javascript-format
|
||||||
|
msgid ""
|
||||||
|
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||||
|
"the emitter note %s is negative."
|
||||||
|
msgstr ""
|
||||||
|
"Attention, La transaction de %s depuis la note %s vers la note %s a été "
|
||||||
|
"réalisée avec succès, mais la note émettrice %s est en négatif."
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:318
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s succeed!"
|
||||||
|
msgstr ""
|
||||||
|
"Le transfert de %s de la note %s vers la note %s a été fait avec succès !"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:325
|
||||||
|
#: apps/note/static/note/js/transfer.js:346
|
||||||
|
#: apps/note/static/note/js/transfer.js:353
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Transfer of %s from %s to %s failed: %s"
|
||||||
|
msgstr "Le transfert de %s de la note %s vers la note %s a échoué : %s"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:347
|
||||||
|
msgid "insufficient funds"
|
||||||
|
msgstr "solde insuffisant"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:400
|
||||||
|
msgid "Credit/debit succeed!"
|
||||||
|
msgstr "Le crédit/retrait a bien été effectué !"
|
||||||
|
|
||||||
|
#: apps/note/static/note/js/transfer.js:407
|
||||||
|
#, javascript-format
|
||||||
|
msgid "Credit/debit failed: %s"
|
||||||
|
msgstr "Le crédit/retrait a échoué : %s"
|
||||||
|
|
||||||
|
#: note_kfet/static/js/base.js:366
|
||||||
|
msgid "An error occured while (in)validating this transaction:"
|
||||||
|
msgstr ""
|
||||||
|
"Une erreur est survenue lors de la validation/dévalidation de cette "
|
||||||
|
"transaction :"
|
@@ -2,12 +2,12 @@
|
|||||||
# 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.contrib.auth import login
|
||||||
from django.contrib.auth.models import AnonymousUser, User
|
from django.contrib.auth.models import AnonymousUser, User
|
||||||
|
from django.contrib.sessions.backends.db import SessionStore
|
||||||
|
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.contrib.sessions.backends.db import SessionStore
|
|
||||||
|
|
||||||
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
|
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
|
||||||
SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
|
SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
|
||||||
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
|
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
|
||||||
@@ -78,6 +78,41 @@ class SessionMiddleware(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class LoginByIPMiddleware(object):
|
||||||
|
"""
|
||||||
|
Allow some users to be authenticated based on their IP address.
|
||||||
|
For example, the "note" account should not be used elsewhere than the Kfet computer,
|
||||||
|
and should not have any password.
|
||||||
|
The password that is stored in database should be on the form "ipbased$my.public.ip.address".
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
"""
|
||||||
|
If the user is not authenticated, get the used IP address
|
||||||
|
and check if an user is authorized to be automatically logged with this address.
|
||||||
|
If it is the case, the logging is performed with the full rights.
|
||||||
|
"""
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
if 'HTTP_X_REAL_IP' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_REAL_IP')
|
||||||
|
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
|
||||||
|
qs = User.objects.filter(password=f"ipbased${ip}")
|
||||||
|
if qs.exists():
|
||||||
|
login(request, qs.get())
|
||||||
|
session = request.session
|
||||||
|
session["permission_mask"] = 42
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
class TurbolinksMiddleware(object):
|
class TurbolinksMiddleware(object):
|
||||||
"""
|
"""
|
||||||
Send the `Turbolinks-Location` header in response to a visit that was redirected,
|
Send the `Turbolinks-Location` header in response to a visit that was redirected,
|
||||||
|
@@ -49,9 +49,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "logs" in INSTALLED_APPS:
|
|
||||||
MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor']
|
PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor']
|
||||||
if "debug_toolbar" in INSTALLED_APPS:
|
if "debug_toolbar" in INSTALLED_APPS:
|
||||||
|
@@ -79,6 +79,8 @@ MIDDLEWARE = [
|
|||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
||||||
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
|
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
|
||||||
|
'note_kfet.middlewares.SessionMiddleware',
|
||||||
|
'note_kfet.middlewares.LoginByIPMiddleware',
|
||||||
'note_kfet.middlewares.TurbolinksMiddleware',
|
'note_kfet.middlewares.TurbolinksMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# CAS
|
|
||||||
OPTIONAL_APPS = [
|
OPTIONAL_APPS = [
|
||||||
# 'debug_toolbar'
|
# 'cas_server',
|
||||||
|
# 'debug_toolbar',
|
||||||
|
# 'django_extensions',
|
||||||
]
|
]
|
||||||
|
|
||||||
# When a server error occured, send an email to these addresses
|
# When a server error occurred, send an email to these addresses
|
||||||
ADMINS = (
|
ADMINS = (
|
||||||
('Note Kfet', 'notekfet@example.com'),
|
('Note Kfet', 'notekfet@example.com'),
|
||||||
)
|
)
|
||||||
|
@@ -363,8 +363,7 @@ function de_validate (id, validated, resourcetype) {
|
|||||||
const errObj = JSON.parse(err.responseText)
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error) { error = err.responseText }
|
if (!error) { error = err.responseText }
|
||||||
addMsg('Une erreur est survenue lors de la validation/dévalidation ' +
|
addMsg(gettext('An error occured while (in)validating this transaction:') + ' ' + error, 'danger')
|
||||||
'de cette transaction : ' + error, 'danger')
|
|
||||||
|
|
||||||
refreshBalance()
|
refreshBalance()
|
||||||
// error if this method doesn't exist. Please define it.
|
// error if this method doesn't exist. Please define it.
|
||||||
|
134
note_kfet/static/js/jsi18n/_default.js
Normal file
134
note_kfet/static/js/jsi18n/_default.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* You should never see this file.
|
||||||
|
* It is here only for compatibility reasons in case of the command `compilejsmessages` was never executed.
|
||||||
|
* Please execute this command to generate translation strings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(globals) {
|
||||||
|
|
||||||
|
var django = globals.django || (globals.django = {});
|
||||||
|
|
||||||
|
|
||||||
|
django.pluralidx = function(n) {
|
||||||
|
var v=(n != 1);
|
||||||
|
if (typeof(v) == 'boolean') {
|
||||||
|
return v ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* gettext library */
|
||||||
|
|
||||||
|
django.catalog = django.catalog || {};
|
||||||
|
|
||||||
|
|
||||||
|
if (!django.jsi18n_initialized) {
|
||||||
|
django.gettext = function(msgid) {
|
||||||
|
var value = django.catalog[msgid];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return msgid;
|
||||||
|
} else {
|
||||||
|
return (typeof(value) == 'string') ? value : value[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
django.ngettext = function(singular, plural, count) {
|
||||||
|
var value = django.catalog[singular];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return (count == 1) ? singular : plural;
|
||||||
|
} else {
|
||||||
|
return value.constructor === Array ? value[django.pluralidx(count)] : value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
django.gettext_noop = function(msgid) { return msgid; };
|
||||||
|
|
||||||
|
django.pgettext = function(context, msgid) {
|
||||||
|
var value = django.gettext(context + '\x04' + msgid);
|
||||||
|
if (value.indexOf('\x04') != -1) {
|
||||||
|
value = msgid;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
django.npgettext = function(context, singular, plural, count) {
|
||||||
|
var value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
|
||||||
|
if (value.indexOf('\x04') != -1) {
|
||||||
|
value = django.ngettext(singular, plural, count);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
django.interpolate = function(fmt, obj, named) {
|
||||||
|
if (named) {
|
||||||
|
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
||||||
|
} else {
|
||||||
|
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* formatting library */
|
||||||
|
|
||||||
|
django.formats = {
|
||||||
|
"DATETIME_FORMAT": "j \\d\\e F \\d\\e Y \\a \\l\\a\\s H:i",
|
||||||
|
"DATETIME_INPUT_FORMATS": [
|
||||||
|
"%d/%m/%Y %H:%M:%S",
|
||||||
|
"%d/%m/%Y %H:%M:%S.%f",
|
||||||
|
"%d/%m/%Y %H:%M",
|
||||||
|
"%d/%m/%y %H:%M:%S",
|
||||||
|
"%d/%m/%y %H:%M:%S.%f",
|
||||||
|
"%d/%m/%y %H:%M",
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
"%Y-%m-%d %H:%M:%S.%f",
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
"%Y-%m-%d"
|
||||||
|
],
|
||||||
|
"DATE_FORMAT": "j \\d\\e F \\d\\e Y",
|
||||||
|
"DATE_INPUT_FORMATS": [
|
||||||
|
"%d/%m/%Y",
|
||||||
|
"%d/%m/%y",
|
||||||
|
"%Y-%m-%d"
|
||||||
|
],
|
||||||
|
"DECIMAL_SEPARATOR": ",",
|
||||||
|
"FIRST_DAY_OF_WEEK": 1,
|
||||||
|
"MONTH_DAY_FORMAT": "j \\d\\e F",
|
||||||
|
"NUMBER_GROUPING": 3,
|
||||||
|
"SHORT_DATETIME_FORMAT": "d/m/Y H:i",
|
||||||
|
"SHORT_DATE_FORMAT": "d/m/Y",
|
||||||
|
"THOUSAND_SEPARATOR": ".",
|
||||||
|
"TIME_FORMAT": "H:i",
|
||||||
|
"TIME_INPUT_FORMATS": [
|
||||||
|
"%H:%M:%S",
|
||||||
|
"%H:%M:%S.%f",
|
||||||
|
"%H:%M"
|
||||||
|
],
|
||||||
|
"YEAR_MONTH_FORMAT": "F \\d\\e Y"
|
||||||
|
};
|
||||||
|
|
||||||
|
django.get_format = function(format_type) {
|
||||||
|
var value = django.formats[format_type];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return format_type;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* add to global namespace */
|
||||||
|
globals.pluralidx = django.pluralidx;
|
||||||
|
globals.gettext = django.gettext;
|
||||||
|
globals.ngettext = django.ngettext;
|
||||||
|
globals.gettext_noop = django.gettext_noop;
|
||||||
|
globals.pgettext = django.pgettext;
|
||||||
|
globals.npgettext = django.npgettext;
|
||||||
|
globals.interpolate = django.interpolate;
|
||||||
|
globals.get_format = django.get_format;
|
||||||
|
|
||||||
|
django.jsi18n_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}(this));
|
||||||
|
|
1
note_kfet/static/js/jsi18n/de.js
Symbolic link
1
note_kfet/static/js/jsi18n/de.js
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
_default.js
|
1
note_kfet/static/js/jsi18n/es.js
Symbolic link
1
note_kfet/static/js/jsi18n/es.js
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
_default.js
|
1
note_kfet/static/js/jsi18n/fr.js
Symbolic link
1
note_kfet/static/js/jsi18n/fr.js
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
_default.js
|
@@ -38,6 +38,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<script src="{% static "js/base.js" %}"></script>
|
<script src="{% static "js/base.js" %}"></script>
|
||||||
<script src="{% static "js/konami.js" %}"></script>
|
<script src="{% static "js/konami.js" %}"></script>
|
||||||
|
|
||||||
|
{# Translation in javascript files #}
|
||||||
|
<script src="{% static "js/jsi18n/jsi18n."|add:LANGUAGE_CODE|add:".js" %}"></script>
|
||||||
|
|
||||||
{# If extra ressources are needed for a form, load here #}
|
{# If extra ressources are needed for a form, load here #}
|
||||||
{% if form.media %}
|
{% if form.media %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
|
@@ -7,7 +7,7 @@ django-extensions~=2.1.4
|
|||||||
django-filter~=2.1.0
|
django-filter~=2.1.0
|
||||||
django-htcpcp-tea~=0.3.1
|
django-htcpcp-tea~=0.3.1
|
||||||
django-mailer~=2.0.1
|
django-mailer~=2.0.1
|
||||||
django-oauth-toolkit~=1.1.2
|
django-oauth-toolkit~=1.3.3
|
||||||
django-phonenumber-field~=5.0.0
|
django-phonenumber-field~=5.0.0
|
||||||
django-polymorphic~=2.0.3
|
django-polymorphic~=2.0.3
|
||||||
djangorestframework~=3.9.0
|
djangorestframework~=3.9.0
|
||||||
|
5
tox.ini
5
tox.ini
@@ -6,6 +6,9 @@ envlist =
|
|||||||
# Ubuntu 20.04 Python
|
# Ubuntu 20.04 Python
|
||||||
py38-django22
|
py38-django22
|
||||||
|
|
||||||
|
# Debian Bullseye Python
|
||||||
|
py39-django22
|
||||||
|
|
||||||
linters
|
linters
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ deps =
|
|||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
coverage
|
coverage
|
||||||
commands =
|
commands =
|
||||||
coverage run --omit='*migrations*,apps/scripts*' --source=apps,note_kfet ./manage.py test apps/
|
coverage run --omit='apps/scripts*,*_example.py,note_kfet/wsgi.py' --source=apps,note_kfet ./manage.py test apps/
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
|
||||||
[testenv:linters]
|
[testenv:linters]
|
||||||
|
Reference in New Issue
Block a user