mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-01 05:21:15 +02:00
Compare commits
73 Commits
6b2638c271
...
faster_ci
Author | SHA1 | Date | |
---|---|---|---|
3eed93e346 | |||
4da523a1ba | |||
e74ff54468 | |||
2e49c9ffbd | |||
d20a1038a8 | |||
f6b711bb1b | |||
893d87a9e1 | |||
9f3323c73e | |||
c57f81b920 | |||
0636d84286 | |||
ed06901fae | |||
28932f316b | |||
9b50ba722c | |||
3e3e61d23f | |||
1129815ca3 | |||
c13172d3ff | |||
fcc4121225 | |||
a06f355559 | |||
08df5fcccd | |||
b6c0f9758d | |||
a23093851f | |||
d5a9bf175f
|
|||
d803ab5ec2 | |||
d7a537b6b5 | |||
0941ee954d | |||
fd11d96d95 | |||
4bfc057454 | |||
b597a6ac5b
|
|||
a704b92c3d | |||
53090b1a21 | |||
c49af0b83a | |||
5a05997d9d
|
|||
c109cd3ddd
|
|||
84304971d7
|
|||
b8b781f9a2 | |||
002128eed2 | |||
8d71783c42 | |||
a6f23df7d5
|
|||
d9c97628e2
|
|||
893534955d
|
|||
dfbf9972c2
|
|||
b5f3b3ffc1
|
|||
3aad4e7398
|
|||
b4a1b513cc
|
|||
c0c64f225c | |||
9d8f47115c
|
|||
f4156f1b94
|
|||
e60994e065
|
|||
801f711994
|
|||
e4568b410f
|
|||
c8f7986d5a | |||
d3a9c442a5
|
|||
016ab5a9c9
|
|||
7866ab7ec0
|
|||
f570ff3cd5
|
|||
5cb4183e9f
|
|||
3a20555663
|
|||
95be0042e9
|
|||
48880e7fd3
|
|||
e0030771e4
|
|||
d47799e6ee
|
|||
eae091625a
|
|||
aceb77ffb9
|
|||
338c94ed05
|
|||
290848f904 | |||
0171f16311 | |||
e1f647bd02 | |||
39fd3a2471 | |||
1072e227b8 | |||
cbf7e6fe6c | |||
950922d041 | |||
78fe070cd3 | |||
51d5733578 |
3
.ansible-lint
Normal file
3
.ansible-lint
Normal file
@ -0,0 +1,3 @@
|
||||
skip_list:
|
||||
- command-instead-of-shell # Use shell only when shell functionality is required
|
||||
- experimental # all rules tagged as experimental
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -47,3 +47,8 @@ backups/
|
||||
env/
|
||||
venv/
|
||||
db.sqlite3
|
||||
|
||||
# ansibles customs host
|
||||
ansible/host_vars/*.yaml
|
||||
!ansible/host_vars/bde*
|
||||
ansible/hosts
|
||||
|
@ -10,35 +10,22 @@ variables:
|
||||
# Debian Buster
|
||||
py37-django22:
|
||||
stage: test
|
||||
image: debian:buster-backports
|
||||
before_script:
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -t buster-backports -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
image: otthorn/nk20_ci_37
|
||||
script: tox -e py37-django22
|
||||
|
||||
# Ubuntu 20.04
|
||||
py38-django22:
|
||||
stage: test
|
||||
image: ubuntu:20.04
|
||||
before_script:
|
||||
# Fix tzdata prompt
|
||||
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||
- >
|
||||
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
|
||||
image: otthorn/nk20_ci_38
|
||||
script: tox -e py38-django22
|
||||
|
||||
# Debian Bullseye
|
||||
py39-django22:
|
||||
stage: test
|
||||
image: otthorn/nk20_ci_39
|
||||
script: tox -e py39-django22
|
||||
|
||||
# Tox linter
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: debian:buster-backports
|
||||
@ -49,6 +36,20 @@ linters:
|
||||
# Be nice to new contributors, but please use `tox`
|
||||
allow_failure: true
|
||||
|
||||
# Ansible linter
|
||||
ansible-linter:
|
||||
stage: quality-assurance
|
||||
image: otthorn/nk20_ci_ansiblelint
|
||||
script: ansible-lint ansible/
|
||||
|
||||
# Docker linter
|
||||
docker-linter:
|
||||
stage: quality-assurance
|
||||
image: hadolint/hadolint
|
||||
script:
|
||||
- hadolint -c .hadolint Dockerfile
|
||||
- hadolint -c .hadolint docker_ci/Dockerfile.*
|
||||
|
||||
# Compile documentation
|
||||
documentation:
|
||||
stage: docs
|
||||
|
4
.hadolint
Normal file
4
.hadolint
Normal file
@ -0,0 +1,4 @@
|
||||
ignored:
|
||||
- DL3008 # Do not force to pin version in apt (Debian)
|
||||
- DL3013 # Do not force to pin version in pip (PyPI)
|
||||
- DL3018 # Do not force to pin version in apk (Alpine)
|
20
README.md
20
README.md
@ -69,13 +69,31 @@ accessible depuis l'ensemble de votre réseau, pratique pour tester le rendu
|
||||
de la note sur un téléphone !
|
||||
|
||||
## Installation d'une instance de production
|
||||
Pour déployer facilement la note il est possible d'utiliser le playbook Ansible (sinon vous pouvez toujours le faire a la main, voir plus bas).
|
||||
### Avec ansible
|
||||
Il vous faudra un serveur sous debian ou ubuntu connecté à internet et que vous souhaiterez accéder à cette instance de la note sur `note.nomdedomaine.tld`.
|
||||
|
||||
0. Installer Ansible sur votre machine personnelle.
|
||||
|
||||
0. (bis) cloner le dépot sur votre machine personelle.
|
||||
|
||||
1. Copier le fichier `ansible/host_example`
|
||||
``` bash
|
||||
$ cp ansible/hosts_example ansible/hosts
|
||||
```
|
||||
et ajouter sous [dev] et/ou [prod] les serveurs sur lesquels vous souhaitez installer la note.
|
||||
2. Créer un fichier `ansible/host_vars/<note.nomdedomaine.tld.yaml>` sur le modèle des fichiers existants dans `ansible/hosts` et compléter les variables nécessaires.
|
||||
|
||||
3. lancer `ansible/base.yaml -l <nomdedomaine.tld.yaml>`
|
||||
4. Aller vous faire un café, ca peux durer un moment.
|
||||
|
||||
### Installation manuelle
|
||||
|
||||
**En production on souhaite absolument utiliser les modules Python packagées dans le gestionnaire de paquet.**
|
||||
Cela permet de mettre à jour facilement les dépendances critiques telles que Django.
|
||||
|
||||
L'installation d'une instance de production néccessite **une installation de Debian Buster ou d'Ubuntu 20.04**.
|
||||
|
||||
Pour aller vite vous pouvez lancer le Playbook Ansible fournit dans ce dépôt en l'adaptant.
|
||||
Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||
|
||||
0. Sous Debian Buster, **activer Debian Backports.** En effet Django 2.2 LTS n'est que disponible dans les backports.
|
||||
|
@ -7,7 +7,7 @@
|
||||
prompt: "Password of the database (leave it blank to skip database init)"
|
||||
private: yes
|
||||
vars:
|
||||
mirror: deb.debian.org
|
||||
mirror: mirror.crans.org
|
||||
roles:
|
||||
- 1-apt-basic
|
||||
- 2-nk20
|
||||
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note-beta.crans.org
|
||||
git_branch: beta
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note-dev.crans.org
|
||||
git_branch: beta
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note.crans.org
|
||||
git_branch: master
|
||||
cron_enabled: true
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -1,5 +1,5 @@
|
||||
[dev]
|
||||
bde3-virt.adh.crans.org
|
||||
bde-note-dev.adh.crans.org
|
||||
bde-nk20-beta.adh.crans.org
|
||||
|
||||
[prod]
|
@ -3,11 +3,12 @@
|
||||
apt_repository:
|
||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||
state: present
|
||||
when: ansible_facts['distribution'] == "Debian"
|
||||
|
||||
- name: Install note_kfet APT dependencies
|
||||
apt:
|
||||
update_cache: true
|
||||
default_release: buster-backports
|
||||
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
||||
install_recommends: false
|
||||
name:
|
||||
# Common tools
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
- name: Use default env vars (should be updated!)
|
||||
template:
|
||||
src: "env_example"
|
||||
src: "env.j2"
|
||||
dest: "/var/www/note_kfet/.env"
|
||||
mode: 0644
|
||||
force: false
|
||||
@ -36,3 +36,13 @@
|
||||
dest: /etc/cron.d/note
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set default directory to /var/www/note_kfet
|
||||
lineinfile:
|
||||
path: /etc/skel/.bashrc
|
||||
line: 'cd /var/www/note_kfet'
|
||||
|
||||
- name: Automatically source Python virtual environment
|
||||
lineinfile:
|
||||
path: /etc/skel/.bashrc
|
||||
line: 'source /var/www/note_kfet/env/bin/activate'
|
||||
|
23
ansible/roles/2-nk20/templates/env.j2
Normal file
23
ansible/roles/2-nk20/templates/env.j2
Normal file
@ -0,0 +1,23 @@
|
||||
DJANGO_APP_STAGE=prod
|
||||
# Only used in dev mode, change to "postgresql" if you want to use PostgreSQL in dev
|
||||
DJANGO_DEV_STORE_METHOD=sqlite
|
||||
DJANGO_DB_HOST=localhost
|
||||
DJANGO_DB_NAME=note_db
|
||||
DJANGO_DB_USER=note
|
||||
DJANGO_DB_PASSWORD={{ DB_PASSWORD }}
|
||||
DJANGO_DB_PORT=
|
||||
DJANGO_SECRET_KEY=CHANGE_ME
|
||||
DJANGO_SETTINGS_MODULE=note_kfet.settings
|
||||
CONTACT_EMAIL=tresorerie.bde@localhost
|
||||
NOTE_URL= {{note.server_name}}
|
||||
|
||||
# Config for mails. Only used in production
|
||||
NOTE_MAIL=notekfet@localhost
|
||||
EMAIL_HOST=smtp.localhost
|
||||
EMAIL_PORT=25
|
||||
EMAIL_USER=notekfet@localhost
|
||||
EMAIL_PASSWORD=CHANGE_ME
|
||||
|
||||
# Wiki configuration
|
||||
WIKI_USER=NoteKfet2020
|
||||
WIKI_PASSWORD=
|
@ -9,6 +9,11 @@
|
||||
retries: 3
|
||||
until: pkg_result is succeeded
|
||||
|
||||
- name: Check if certificate already exists.
|
||||
stat:
|
||||
path: /etc/letsencrypt/live/{{note.server_name}}/cert.pem
|
||||
register: letsencrypt_cert
|
||||
|
||||
- name: Create /etc/letsencrypt/conf.d
|
||||
file:
|
||||
path: /etc/letsencrypt/conf.d
|
||||
@ -19,3 +24,17 @@
|
||||
src: "letsencrypt/conf.d/nk20.ini.j2"
|
||||
dest: "/etc/letsencrypt/conf.d/nk20.ini"
|
||||
mode: 0644
|
||||
|
||||
- name: Stop services to allow certbot to generate a cert.
|
||||
service:
|
||||
name: nginx
|
||||
state: stopped
|
||||
|
||||
- name: Generate new certificate if one doesn't exist.
|
||||
shell: "certbot certonly --non-interactive --agree-tos --config /etc/letsencrypt/conf.d/nk20.ini -d {{note.server_name}}"
|
||||
when: letsencrypt_cert.stat.exists == False
|
||||
|
||||
- name: Restart services to allow certbot to generate a cert.
|
||||
service:
|
||||
name: nginx
|
||||
state: started
|
||||
|
@ -10,7 +10,7 @@ rsa-key-size = 4096
|
||||
# server = https://acme-staging.api.letsencrypt.org/directory
|
||||
|
||||
# Uncomment and update to register with the specified e-mail address
|
||||
email = notekfet2020@lists.crans.org
|
||||
email = {{ note.email }}
|
||||
|
||||
# Uncomment to use a text interface instead of ncurses
|
||||
text = True
|
||||
|
@ -11,14 +11,14 @@
|
||||
until: pkg_result is succeeded
|
||||
|
||||
- name: Create role note
|
||||
when: "DB_PASSWORD|bool" # If the password is not defined, skip the installation
|
||||
when: DB_PASSWORD|length > 0 # If the password is not defined, skip the installation
|
||||
postgresql_user:
|
||||
name: note
|
||||
password: "{{ DB_PASSWORD }}"
|
||||
become_user: postgres
|
||||
|
||||
- name: Create NK20 database
|
||||
when: "DB_PASSWORD|bool"
|
||||
when: DB_PASSWORD|length >0
|
||||
postgresql_db:
|
||||
name: note_db
|
||||
owner: note
|
||||
|
@ -15,10 +15,10 @@ class ActivityTypeViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/type/
|
||||
"""
|
||||
queryset = ActivityType.objects.all()
|
||||
queryset = ActivityType.objects.order_by('id')
|
||||
serializer_class = ActivityTypeSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'can_invite', ]
|
||||
filterset_fields = ['name', 'manage_entries', 'can_invite', 'guest_entry_fee', ]
|
||||
|
||||
|
||||
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,
|
||||
then render it on /api/activity/activity/
|
||||
"""
|
||||
queryset = Activity.objects.all()
|
||||
queryset = Activity.objects.order_by('id')
|
||||
serializer_class = ActivitySerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'description', 'activity_type', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
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):
|
||||
@ -39,10 +45,13 @@ class GuestViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/guest/
|
||||
"""
|
||||
queryset = Guest.objects.all()
|
||||
queryset = Guest.objects.order_by('id')
|
||||
serializer_class = GuestSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
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):
|
||||
@ -51,7 +60,9 @@ class EntryViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/entry/
|
||||
"""
|
||||
queryset = Entry.objects.all()
|
||||
queryset = Entry.objects.order_by('id')
|
||||
serializer_class = EntrySerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
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', ]
|
||||
|
@ -3,13 +3,16 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from activity.models import Activity, ActivityType, Guest, Entry
|
||||
from member.models import Club
|
||||
|
||||
from ..api.views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||
from ..models import Activity, ActivityType, Guest, Entry
|
||||
|
||||
|
||||
class TestActivities(TestCase):
|
||||
"""
|
||||
@ -173,3 +176,58 @@ class TestActivities(TestCase):
|
||||
"""
|
||||
response = self.client.get(reverse("activity:calendar_ics"))
|
||||
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.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.filters import SearchFilter
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
||||
from permission.backends import PermissionBackend
|
||||
from note_kfet.middlewares import get_current_session
|
||||
@ -48,12 +49,13 @@ class UserViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
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
|
||||
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):
|
||||
queryset = super().get_queryset()
|
||||
@ -106,7 +108,10 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
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
|
||||
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,
|
||||
then render it on /api/logs/
|
||||
"""
|
||||
queryset = Changelog.objects.all()
|
||||
queryset = Changelog.objects.order_by('id')
|
||||
serializer_class = ChangelogSerializer
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# 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 .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,
|
||||
then render it on /api/members/profile/
|
||||
"""
|
||||
queryset = Profile.objects.all()
|
||||
queryset = Profile.objects.order_by('id')
|
||||
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):
|
||||
@ -24,10 +32,13 @@ class ClubViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/club/
|
||||
"""
|
||||
queryset = Club.objects.all()
|
||||
queryset = Club.objects.order_by('id')
|
||||
serializer_class = ClubSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
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):
|
||||
@ -36,5 +47,14 @@ class MembershipViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/membership/
|
||||
"""
|
||||
queryset = Membership.objects.all()
|
||||
queryset = Membership.objects.order_by('id')
|
||||
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', ]
|
||||
|
@ -313,6 +313,7 @@ class Membership(models.Model):
|
||||
|
||||
roles = models.ManyToManyField(
|
||||
"permission.Role",
|
||||
related_name="memberships",
|
||||
verbose_name=_("roles"),
|
||||
)
|
||||
|
||||
|
@ -48,7 +48,7 @@
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans 'Manage aliases' %} ({{ club.note.alias_set.all|length }})
|
||||
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||
<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>
|
||||
</dd>
|
||||
|
||||
|
@ -5,17 +5,20 @@ import hashlib
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from member.models import Club, Membership, Profile
|
||||
from note.models import Alias, NoteSpecial
|
||||
from permission.models import Role
|
||||
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
|
||||
and that memberships are working.
|
||||
@ -403,3 +406,46 @@ class TestMemberships(TestCase):
|
||||
self.user.password = "custom_nk15$1$" + salt + "|" + hashed
|
||||
self.user.save()
|
||||
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/")
|
||||
|
@ -256,7 +256,7 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(
|
||||
note.alias_set.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().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(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
@ -458,7 +458,7 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(note.alias_set.filter(
|
||||
context["aliases"] = AliasTable(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(
|
||||
note=context["object"].note,
|
||||
@ -658,8 +658,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
||||
user=form.instance.user,
|
||||
club=club.parent_club,
|
||||
date_start__gte=club.parent_club.membership_start,
|
||||
date_end__lte=club.parent_club.membership_end,
|
||||
date_start__lte=timezone.now(),
|
||||
date_end__gte=club.parent_club.membership_end,
|
||||
).exists():
|
||||
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
||||
error = True
|
||||
|
@ -15,29 +15,37 @@ from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||
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
|
||||
|
||||
|
||||
class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
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/
|
||||
"""
|
||||
queryset = Note.objects.all()
|
||||
queryset = Note.objects.order_by('id')
|
||||
serializer_class = NotePolymorphicSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['polymorphic_ctype', 'is_active', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
|
||||
ordering_fields = ['alias__name', 'alias__normalized_name']
|
||||
filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model',
|
||||
'$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):
|
||||
"""
|
||||
Parse query and apply filters.
|
||||
: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", ".*")
|
||||
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,
|
||||
then render it on /api/aliases/
|
||||
"""
|
||||
queryset = Alias.objects.all()
|
||||
queryset = Alias.objects
|
||||
serializer_class = AliasSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['note']
|
||||
ordering_fields = ['name', 'normalized_name']
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
@ -106,12 +114,12 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
|
||||
|
||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
queryset = Alias.objects.all()
|
||||
queryset = Alias.objects
|
||||
serializer_class = ConsumerSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['note']
|
||||
ordering_fields = ['name', 'normalized_name']
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
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,
|
||||
then render it on /api/note/transaction/category/
|
||||
"""
|
||||
queryset = TemplateCategory.objects.order_by("name").all()
|
||||
queryset = TemplateCategory.objects.order_by('name')
|
||||
serializer_class = TemplateCategorySerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'templates', 'templates__name']
|
||||
search_fields = ['$name', '$templates__name', ]
|
||||
|
||||
|
||||
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,
|
||||
then render it on /api/note/transaction/template/
|
||||
"""
|
||||
queryset = TransactionTemplate.objects.order_by("name").all()
|
||||
queryset = TransactionTemplate.objects.order_by('name')
|
||||
serializer_class = TransactionTemplateSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ]
|
||||
search_fields = ['$name', '$category__name', ]
|
||||
ordering_fields = ['amount', ]
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ["source", "source_alias", "destination", "destination_alias", "quantity",
|
||||
"polymorphic_ctype", "amount", "created_at", ]
|
||||
search_fields = ['$reason', ]
|
||||
ordering_fields = ['created_at', 'amount']
|
||||
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
|
||||
'destination', 'destination_alias', 'destination__alias__name',
|
||||
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', '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):
|
||||
user = self.request.user
|
||||
|
@ -248,6 +248,7 @@ class Alias(models.Model):
|
||||
note = models.ForeignKey(
|
||||
Note,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="alias",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -223,7 +223,8 @@ class Transaction(PolymorphicModel):
|
||||
# Check that the amounts stay between big integer bounds
|
||||
diff_source, diff_dest = self.validate()
|
||||
|
||||
if not self.source.is_active or not self.destination.is_active:
|
||||
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||
and (not self.source.is_active or not self.destination.is_active):
|
||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||
"or the destination note is not active."))
|
||||
|
||||
@ -271,7 +272,7 @@ class RecurrentTransaction(Transaction):
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if self.template.destination != self.destination:
|
||||
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
||||
raise ValidationError(
|
||||
_("The destination of this transaction must equal to the destination of the template."))
|
||||
return super().clean()
|
||||
|
@ -43,4 +43,5 @@ def delete_transaction(instance, **_kwargs):
|
||||
"""
|
||||
if not hasattr(instance, "_no_signal"):
|
||||
instance.valid = False
|
||||
instance._force_save = True
|
||||
instance.save()
|
||||
|
@ -223,13 +223,14 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
const newBalance = source.balance - quantity * amount
|
||||
if (newBalance <= -5000) {
|
||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||
'but the emitter note %s is very negative.', [source_alias, source_alias])), 'danger', 30000)
|
||||
'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000)
|
||||
} else if (newBalance < 0) {
|
||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||
'but the emitter note %s is negative.', [source_alias, source_alias])), 'warning', 30000)
|
||||
'but the emitter note %s is negative.'), [source_alias, source_alias]), 'warning', 30000)
|
||||
}
|
||||
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.', [source_alias])), 'danger', 30000)
|
||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source_alias]),
|
||||
'danger', 30000)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
|
@ -302,7 +302,7 @@ $('#btn_transfer').click(function () {
|
||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
||||
}
|
||||
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
||||
addMsg(interpolate(gettext('Warning, the destination note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
||||
addMsg(interpolate(gettext('Warning, the destination note %s is no more a BDE member.'), [dest.name]), 'danger', 30000)
|
||||
}
|
||||
|
||||
if (!isNaN(source.note.balance)) {
|
||||
|
@ -1,15 +1,20 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# 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.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from member.models import Club, Membership
|
||||
from note.models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias
|
||||
from django.utils import timezone
|
||||
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):
|
||||
fixtures = ('initial', )
|
||||
@ -297,8 +302,8 @@ class TestTransactions(TestCase):
|
||||
|
||||
def test_render_search_transactions(self):
|
||||
response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
|
||||
source=self.second_user.note.alias_set.first().id,
|
||||
destination=self.user.note.alias_set.first().id,
|
||||
source=self.second_user.note.alias.first().id,
|
||||
destination=self.user.note.alias.first().id,
|
||||
type=[ContentType.objects.get_for_model(Transaction).id],
|
||||
reason="test",
|
||||
valid=True,
|
||||
@ -363,3 +368,69 @@ class TestTransactions(TestCase):
|
||||
self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
|
||||
response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
|
||||
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
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
|
||||
from .serializers import PermissionSerializer, RoleSerializer
|
||||
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,
|
||||
then render it on /api/permission/permission/
|
||||
"""
|
||||
queryset = Permission.objects.all()
|
||||
queryset = Permission.objects.order_by('id')
|
||||
serializer_class = PermissionSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['model', 'type', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['model', 'type', 'query', 'mask', 'field', 'permanent', ]
|
||||
search_fields = ['$model__name', '$query', '$description', ]
|
||||
|
||||
|
||||
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
|
||||
then render it on /api/permission/roles/
|
||||
"""
|
||||
queryset = Role.objects.all()
|
||||
queryset = Role.objects.order_by('id')
|
||||
serializer_class = RoleSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['role', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'permissions', 'for_club', 'memberships__user', ]
|
||||
search_fields = ['$name', '$for_club__name', ]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from time import time
|
||||
|
||||
@ -38,6 +38,10 @@ def memoize(f):
|
||||
|
||||
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:
|
||||
# Clear cache
|
||||
collect()
|
||||
|
@ -819,7 +819,7 @@
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"permanent": true,
|
||||
"description": "Modifier son profil"
|
||||
}
|
||||
},
|
||||
@ -3024,7 +3024,9 @@
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27
|
||||
27,
|
||||
30,
|
||||
33
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -5,7 +5,6 @@ from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django import template
|
||||
from note.models import Transaction
|
||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
@ -25,21 +24,6 @@ def not_empty_model_list(model_name):
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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.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_length', model_list_length)
|
||||
register.filter('has_perm', has_perm)
|
||||
|
@ -78,7 +78,7 @@ class PermissionQueryTestCase(TestCase):
|
||||
query = instanced.query
|
||||
model = perm.model.model_class()
|
||||
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:", perm.query)
|
||||
if instanced.query:
|
||||
|
@ -85,7 +85,7 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView):
|
||||
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.
|
||||
It should be valid (can be stored properly in database), but must not collide with existing data.
|
||||
|
Submodule apps/scripts updated: dbe7bf6591...8ec7d68a16
@ -16,10 +16,11 @@ class InvoiceViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/invoice/
|
||||
"""
|
||||
queryset = Invoice.objects.order_by("id").all()
|
||||
queryset = Invoice.objects.order_by('id')
|
||||
serializer_class = InvoiceSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['bde', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['bde', 'object', 'description', 'name', 'address', 'date', 'acquitted', 'locked', ]
|
||||
search_fields = ['$object', '$description', '$name', '$address', ]
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$designation', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['invoice', 'designation', 'quantity', 'amount', ]
|
||||
search_fields = ['$designation', '$invoice__object', ]
|
||||
|
||||
|
||||
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
|
||||
then render it on /api/treasury/remittance_type/
|
||||
"""
|
||||
queryset = RemittanceType.objects.order_by("id")
|
||||
queryset = RemittanceType.objects.order_by('id')
|
||||
serializer_class = RemittanceTypeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['note', ]
|
||||
search_fields = ['$note__special_type', ]
|
||||
|
||||
|
||||
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,
|
||||
then render it on /api/treasury/remittance/
|
||||
"""
|
||||
queryset = Remittance.objects.order_by("id")
|
||||
queryset = Remittance.objects.order_by('id')
|
||||
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):
|
||||
@ -60,5 +68,10 @@ class SogeCreditViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/soge_credit/
|
||||
"""
|
||||
queryset = SogeCredit.objects.order_by("id")
|
||||
queryset = SogeCredit.objects.order_by('id')
|
||||
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', ]
|
||||
|
@ -257,6 +257,7 @@ class SpecialTransactionProxy(models.Model):
|
||||
Remittance,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
related_name="transaction_proxies",
|
||||
verbose_name=_("Remittance"),
|
||||
)
|
||||
|
||||
@ -380,9 +381,14 @@ class SogeCredit(models.Model):
|
||||
tr.valid = True
|
||||
tr.created_at = timezone.now()
|
||||
tr.save()
|
||||
self.credit_transaction.valid = False
|
||||
self.credit_transaction.reason += " (invalide)"
|
||||
self.credit_transaction.save()
|
||||
if self.credit_transaction:
|
||||
# If the soge credit is deleted while the user is not validated yet,
|
||||
# there is not credit transaction.
|
||||
# There is a credit transaction iff the user declares that no bank account
|
||||
# was opened after the validation of the account.
|
||||
self.credit_transaction.valid = False
|
||||
self.credit_transaction.reason += " (invalide)"
|
||||
self.credit_transaction.save()
|
||||
super().delete(**kwargs)
|
||||
|
||||
class Meta:
|
||||
|
@ -109,9 +109,6 @@ class SpecialTransactionTable(tables.Table):
|
||||
'a': {'class': 'btn btn-primary btn-danger'}
|
||||
}, )
|
||||
|
||||
def render_id(self, record):
|
||||
return record.specialtransactionproxy.pk
|
||||
|
||||
def render_amount(self, value):
|
||||
return pretty_money(value)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
@ -8,7 +9,10 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from member.models import Membership, Club
|
||||
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):
|
||||
@ -366,11 +370,8 @@ class TestSogeCredits(TestCase):
|
||||
response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
try:
|
||||
self.client.post(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
|
||||
self.assertRaises(ValidationError, self.client.post,
|
||||
reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True))
|
||||
|
||||
SpecialTransaction.objects.create(
|
||||
source=NoteSpecial.objects.get(special_type="Carte bancaire"),
|
||||
@ -399,3 +400,82 @@ class TestSogeCredits(TestCase):
|
||||
"""
|
||||
response = self.client.get("/api/treasury/soge_credit/")
|
||||
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
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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 .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,
|
||||
then render it on /api/wei/club/
|
||||
"""
|
||||
queryset = WEIClub.objects.all()
|
||||
queryset = WEIClub.objects.order_by('id')
|
||||
serializer_class = WEIClubSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'year', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'year', 'date_start', 'date_end', '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 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,
|
||||
then render it on /api/wei/bus/
|
||||
"""
|
||||
queryset = Bus.objects
|
||||
queryset = Bus.objects.order_by('id')
|
||||
serializer_class = BusSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'wei', 'description', ]
|
||||
search_fields = ['$name', '$wei__name', '$description', ]
|
||||
|
||||
|
||||
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,
|
||||
then render it on /api/wei/team/
|
||||
"""
|
||||
queryset = BusTeam.objects
|
||||
queryset = BusTeam.objects.order_by('id')
|
||||
serializer_class = BusTeamSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'bus', 'bus__wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ]
|
||||
search_fields = ['$name', '$bus__name', '$bus__wei__name', '$description', ]
|
||||
|
||||
|
||||
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,
|
||||
then render it on /api/wei/role/
|
||||
"""
|
||||
queryset = WEIRole.objects
|
||||
queryset = WEIRole.objects.order_by('id')
|
||||
serializer_class = WEIRoleSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'permissions', 'memberships', ]
|
||||
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,
|
||||
then render it on /api/wei/registration/
|
||||
"""
|
||||
queryset = WEIRegistration.objects
|
||||
queryset = WEIRegistration.objects.order_by('id')
|
||||
serializer_class = WEIRegistrationSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$user__username', ]
|
||||
filterset_fields = ['user', 'wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
|
||||
'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):
|
||||
@ -79,8 +90,16 @@ class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/membership/
|
||||
"""
|
||||
queryset = WEIMembership.objects
|
||||
queryset = WEIMembership.objects.order_by('id')
|
||||
serializer_class = WEIMembershipSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$user__username', '$bus__name', '$team__name', ]
|
||||
filterset_fields = ['user', 'club', 'bus', 'team', ]
|
||||
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', '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>
|
||||
{% 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
|
||||
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 %}
|
||||
|
||||
<dt class="col-xl-4">{% trans 'email'|capfirst %}</dt>
|
||||
|
@ -4,16 +4,19 @@
|
||||
import subprocess
|
||||
from datetime import timedelta, date
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from member.models import Membership
|
||||
from member.models import Membership, Club
|
||||
from note.models import NoteClub, SpecialTransaction
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
from ..api.views import BusViewSet, BusTeamViewSet, WEIClubViewSet, WEIMembershipViewSet, WEIRegistrationViewSet, \
|
||||
WEIRoleViewSet
|
||||
from ..forms import CurrentSurvey, WEISurveyAlgorithm, WEISurvey
|
||||
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
|
||||
|
||||
@ -524,7 +527,7 @@ class TestWEIRegistration(TestCase):
|
||||
sess["permission_mask"] = 0
|
||||
sess.save()
|
||||
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.save()
|
||||
|
||||
@ -807,3 +810,97 @@ class TestWEISurveyAlgorithm(TestCase):
|
||||
|
||||
def test_survey_algorithm(self):
|
||||
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/")
|
||||
|
18
docker_ci/Dockerfile.37
Normal file
18
docker_ci/Dockerfile.37
Normal file
@ -0,0 +1,18 @@
|
||||
FROM debian:buster-backports
|
||||
|
||||
LABEL maintainer="otthorn@crans.org"
|
||||
LABEL description="Debian Buster backports image with django and tox \
|
||||
installed for testing purposes"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -t buster-backports -y \
|
||||
python3-django python3-django-crispy-forms \
|
||||
python3-django-extensions python3-django-filters \
|
||||
python3-django-polymorphic \
|
||||
python3-djangorestframework python3-django-oauth-toolkit \
|
||||
python3-psycopg2 python3-pil \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||
python3-memcache \
|
||||
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
22
docker_ci/Dockerfile.38
Normal file
22
docker_ci/Dockerfile.38
Normal file
@ -0,0 +1,22 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
LABEL maintainer="otthorn@crans.org"
|
||||
LABEL description="Ubuntu 20.04 image with django and tox \
|
||||
installed for testing purposes"
|
||||
|
||||
# fix tzdata prompt
|
||||
RUN ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||
|
||||
RUN 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 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
18
docker_ci/Dockerfile.39
Normal file
18
docker_ci/Dockerfile.39
Normal file
@ -0,0 +1,18 @@
|
||||
FROM debian:bullseye
|
||||
|
||||
LABEL maintainer="otthorn@crans.org"
|
||||
LABEL description="Debian Bulleye image with django and tox \
|
||||
installed for testing purposes"
|
||||
|
||||
RUN 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 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
10
docker_ci/Dockerfile.ansiblelint
Normal file
10
docker_ci/Dockerfile.ansiblelint
Normal file
@ -0,0 +1,10 @@
|
||||
FROM python:3.9-alpine
|
||||
|
||||
LABEL maintainer="otthorn@crans.org"
|
||||
LABEL description="Alpine image with ansible-lint and yamllint \
|
||||
installed for linting purposes"
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev cargo
|
||||
RUN pip install --no-cache-dir "yamllint>=1.26.0,<2.0"
|
||||
RUN pip install --no-cache-dir "ansible-lint==5.0.0"
|
||||
RUN pip install --no-cache-dir "ansible>=2.10,<2.11"
|
8
docker_ci/Dockerfile.tox
Normal file
8
docker_ci/Dockerfile.tox
Normal file
@ -0,0 +1,8 @@
|
||||
FROM alpine:3.13
|
||||
|
||||
LABEL maintainer="otthorn@crans.org"
|
||||
LABEL description="Alpine image with tox \
|
||||
installed for linting purposes"
|
||||
|
||||
RUN apk --no-cache add py3-pip=20.3.4-r0
|
||||
RUN pip install --no-cache-dir tox==3.22.0
|
21
docker_ci/README.md
Normal file
21
docker_ci/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Docker CI
|
||||
|
||||
Ce dossier contient les images docker à construire pour la CI. L'idée est
|
||||
d'avoir une image pré-construire, au dessus laquel il y a besoin de faire
|
||||
tourner uniquement les commandes qui nous intéresse. Cela permet notamment de
|
||||
réduire drastiquement le temps que nécessite chaque test car seul la dernière
|
||||
couche (layer) de l'image a besoin d'etre éxécuter.
|
||||
|
||||
## Build les images
|
||||
|
||||
Pour build les images il suffit de lancer les commandes suivantes
|
||||
|
||||
```
|
||||
cd docker_ci/
|
||||
docker build -t nk20_ci_37 -f Dockerfile.37 .
|
||||
docker build -t nk20_ci_38 -f Dockerfile.38 .
|
||||
docker build -t nk20_ci_39 -f Dockerfile.39 .
|
||||
```
|
||||
|
||||
Elles sont acutellement build et disponible sur dockerhub
|
||||
https://hub.docker.com/otthorn/nk20_ci_37
|
370
docs/api/activity.rst
Normal file
370
docs/api/activity.rst
Normal file
@ -0,0 +1,370 @@
|
||||
API Activités
|
||||
=============
|
||||
|
||||
Activité
|
||||
--------
|
||||
|
||||
**Chemin :** `/api/activity/activity/ <https://note.crans.org/api/activity/activity/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Activity List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer,\nthen render it on /api/activity/activity/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Description"
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Lieu",
|
||||
"help_text": "Lieu o\u00f9 l'activit\u00e9 est organis\u00e9e, par exemple la Kfet.",
|
||||
"max_length": 255
|
||||
},
|
||||
"date_start": {
|
||||
"type": "datetime",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Date de d\u00e9but"
|
||||
},
|
||||
"date_end": {
|
||||
"type": "datetime",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Date de fin"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Valide"
|
||||
},
|
||||
"open": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Ouvrir"
|
||||
},
|
||||
"activity_type": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Type"
|
||||
},
|
||||
"creater": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Utilisateur"
|
||||
},
|
||||
"organizer": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Organisateur",
|
||||
"help_text": "Le club qui organise l'activit\u00e9. Les co\u00fbts d'invitation iront pour ce club."
|
||||
},
|
||||
"attendees_club": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Club attendu",
|
||||
"help_text": "Club qui est autoris\u00e9 \u00e0 rejoindre l'activit\u00e9. Tr\u00e8s souvent le club Kfet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``description``
|
||||
* ``activity_type``
|
||||
* ``location``
|
||||
* ``creater``
|
||||
* ``organizer``
|
||||
* ``attendees_club``
|
||||
* ``date_start``
|
||||
* ``date_end``
|
||||
* ``valid``
|
||||
* ``open``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``description`` (expression régulière)
|
||||
* ``location`` (expression régulière)
|
||||
* ``creater__last_name`` (expression régulière)
|
||||
* ``creater__first_name`` (expression régulière)
|
||||
* ``creater__email`` (expression régulière)
|
||||
* ``creater__note__alias__name`` (expression régulière)
|
||||
* ``creater__note__alias__normalized_name`` (expression régulière)
|
||||
* ``organizer__name`` (expression régulière)
|
||||
* ``organizer__email`` (expression régulière)
|
||||
* ``organizer__note__alias__name`` (expression régulière)
|
||||
* ``organizer__note__alias__normalized_name`` (expression régulière)
|
||||
* ``attendees_club__name`` (expression régulière)
|
||||
* ``attendees_club__email`` (expression régulière)
|
||||
* ``attendees_club__note__alias__name`` (expression régulière)
|
||||
* ``attendees_club__note__alias__normalized_name`` (expression régulière)
|
||||
|
||||
Type d'activité
|
||||
---------------
|
||||
|
||||
**Chemin :** `/api/activity/type/ <https://note.crans.org/api/activity/type/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Activity Type List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,\nthen render it on /api/activity/type/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"manage_entries": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "G\u00e9rer les entr\u00e9es",
|
||||
"help_text": "Activer le support des entr\u00e9es pour cette activit\u00e9."
|
||||
},
|
||||
"can_invite": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Peut inviter"
|
||||
},
|
||||
"guest_entry_fee": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cotisation de l'entr\u00e9e invit\u00e9",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``manage_entries``
|
||||
* ``can_invite``
|
||||
* ``guest_entry_fee``
|
||||
|
||||
Invité
|
||||
------
|
||||
|
||||
**Chemin :** `/api/activity/guest/ <https://note.crans.org/api/activity/guest/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Guest List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,\nthen render it on /api/activity/guest/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom de famille",
|
||||
"max_length": 255
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Pr\u00e9nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"activity": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Activity"
|
||||
},
|
||||
"inviter": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "H\u00f4te"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``activity``
|
||||
* ``activity__name``
|
||||
* ``last_name``
|
||||
* ``first_name``
|
||||
* ``inviter``
|
||||
* ``inviter__alias__name``
|
||||
* ``inviter__alias__normalized_name``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``activity__name`` (expression régulière)
|
||||
* ``last_name`` (expression régulière)
|
||||
* ``first_name`` (expression régulière)
|
||||
* ``inviter__user__email`` (expression régulière)
|
||||
* ``inviter__alias__name`` (expression régulière)
|
||||
* ``inviter__alias__normalized_name`` (expression régulière)
|
||||
|
||||
Entrée
|
||||
------
|
||||
|
||||
**Chemin :** `/api/activity/entry/ <https://note.crans.org/api/activity/entry/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Entry List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,\nthen render it on /api/activity/entry/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"time": {
|
||||
"type": "datetime",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Heure d'entr\u00e9e"
|
||||
},
|
||||
"activity": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Activit\u00e9"
|
||||
},
|
||||
"note": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Note"
|
||||
},
|
||||
"guest": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``activity``
|
||||
* ``time``
|
||||
* ``note``
|
||||
* ``guest``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``activity__name`` (expression régulière)
|
||||
* ``note__user__email`` (expression régulière)
|
||||
* ``note__alias__name`` (expression régulière)
|
||||
* ``note__alias__normalized_name`` (expression régulière)
|
||||
* ``guest__last_name`` (expression régulière)
|
||||
* ``guest__first_name`` (expression régulière)
|
||||
|
157
docs/api/basic.rst
Normal file
157
docs/api/basic.rst
Normal file
@ -0,0 +1,157 @@
|
||||
API générale
|
||||
============
|
||||
|
||||
Utilisateur
|
||||
-----------
|
||||
|
||||
**Chemin :** `/api/user/ <https://note.crans.org/api/user/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "User List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,\nthen render it on /api/user/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"last_login": {
|
||||
"type": "datetime",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Derni\u00e8re connexion"
|
||||
},
|
||||
"is_superuser": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Statut super-utilisateur",
|
||||
"help_text": "Pr\u00e9cise que l'utilisateur poss\u00e8de toutes les permissions sans les assigner explicitement."
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Pseudo",
|
||||
"help_text": "Requis. 150 caract\u00e8res maximum. Uniquement des lettres, nombres et les caract\u00e8res \u00ab\u00a0@\u00a0\u00bb, \u00ab\u00a0.\u00a0\u00bb, \u00ab\u00a0+\u00a0\u00bb, \u00ab\u00a0-\u00a0\u00bb et \u00ab\u00a0_\u00a0\u00bb.",
|
||||
"max_length": 150
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Pr\u00e9nom",
|
||||
"max_length": 30
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Nom de famille",
|
||||
"max_length": 150
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Adresse \u00e9lectronique",
|
||||
"max_length": 254
|
||||
},
|
||||
"is_staff": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Statut \u00e9quipe",
|
||||
"help_text": "Pr\u00e9cise si l'utilisateur peut se connecter \u00e0 ce site d'administration."
|
||||
},
|
||||
"is_active": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Actif",
|
||||
"help_text": "Pr\u00e9cise si l'utilisateur doit \u00eatre consid\u00e9r\u00e9 comme actif. D\u00e9cochez ceci plut\u00f4t que de supprimer le compte."
|
||||
},
|
||||
"date_joined": {
|
||||
"type": "datetime",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Date d'inscription"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``id``
|
||||
* ``username``
|
||||
* ``first_name``
|
||||
* ``last_name``
|
||||
* ``email``
|
||||
* ``is_superuser``
|
||||
* ``is_staff``
|
||||
* ``is_active``
|
||||
* ``note__alias__name``
|
||||
* ``note__alias__normalized_name``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``note__alias`` (expression régulière, cherche en priorité les alias les plus proches, puis cherche les alias normalisés)
|
||||
* ``last_name`` (expression régulière)
|
||||
* ``first_name`` (expression régulière)
|
||||
|
||||
Type de contenu
|
||||
---------------
|
||||
|
||||
**Chemin :** `/api/models/ <https://note.crans.org/api/models/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Content Type List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,\nthen render it on /api/models/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
]
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``id``
|
||||
* ``app_label``
|
||||
* ``model``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``app_label`` (expression régulière)
|
||||
* ``model`` (expression régulière)
|
||||
|
@ -1,6 +1,19 @@
|
||||
API
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Applications
|
||||
|
||||
activity
|
||||
basic
|
||||
logs
|
||||
member
|
||||
note
|
||||
permission
|
||||
treasury
|
||||
wei
|
||||
|
||||
La NoteKfet2020 dispose d'une API REST. Elle est accessible sur `/api/ <https://note.crans.org/api/>`_.
|
||||
Elle supporte les requêtes GET, POST, HEAD, PUT, PATCH et DELETE (peut varier selon les pages).
|
||||
|
||||
@ -9,28 +22,35 @@ Pages de l'API
|
||||
|
||||
Il suffit d'ajouter le préfixe ``/api/`` pour arriver sur ces pages.
|
||||
|
||||
* ``models`` (liste des différents modèles enregistrés en base de données)
|
||||
* ``user`` (liste des différents utilisateurs enregistrés)
|
||||
* ``members/profile`` (liste des différents profils associés à des utilisateurs)
|
||||
* ``members/club`` (liste des différents clubs enregistrés)
|
||||
* ``members/role`` (liste des différents rôles au sein des clubs existant)
|
||||
* ``members/membership`` (liste des adhésions enregistrées)
|
||||
* ``activity/activity`` (liste des activités recensées)
|
||||
* ``activity/type`` (liste des différents types d'activités : pots, soirées de club, ...)
|
||||
* ``activity/guest`` (liste des personnes invitées lors d'une activité)
|
||||
* ``activity/entry`` (liste des entrées effectuées lors des activités)
|
||||
* ``note/note`` (liste des notes enregistrées)
|
||||
* ``note/alias`` (liste des alias enregistrés)
|
||||
* ``note/transaction/category`` (liste des différentes catégories de boutons : soft, alcool, ...)
|
||||
* ``note/transaction/transaction`` (liste des transactions effectuées)
|
||||
* ``note/transaction/template`` (liste des boutons enregistrés)
|
||||
* ``treasury/invoice`` (liste des factures générées)
|
||||
* ``treasury/product`` (liste des produits associés à des factures)
|
||||
* ``treasury/remittance_type`` (liste des types de remises supportés : chèque)
|
||||
* ``treasury/remittance`` (liste des différentes remises enregistrées)
|
||||
* ``permission/permission`` (liste de toutes les permissions enregistrées)
|
||||
* ``permission/roles`` (liste des permissions octroyées pour chacun des rôles)
|
||||
* ``logs`` (liste des modifications enregistrées en base de données)
|
||||
* `models <basic#type-de-contenu>`_ : liste des différents modèles enregistrés en base de données
|
||||
* `user <basic#utilisateur>`_ : liste des différents utilisateurs enregistrés
|
||||
* `members/profile <member#profil-utilisateur>`_ : liste des différents profils associés à des utilisateurs
|
||||
* `members/club <member#club>`_ : liste des différents clubs enregistrés
|
||||
* `members/membership <member#adhesion>`_ : liste des adhésions enregistrées
|
||||
* `activity/activity <activity#activite>`_ : liste des activités recensées
|
||||
* `activity/type <activity#type-d-activite>`_ : liste des différents types d'activités : pots, soirées de club, ...
|
||||
* `activity/guest <activity#invite>`_ : liste des personnes invitées lors d'une activité
|
||||
* `activity/entry <activity#entree>`_ : liste des entrées effectuées lors des activités
|
||||
* `note/note <note#note>`_ : liste des notes enregistrées
|
||||
* `note/alias <note#alias>`_ : liste des alias enregistrés
|
||||
* `note/consumer <note#consommateur>`_ : liste des alias enregistrés avec leur note associée
|
||||
* `note/transaction/category <note#categorie-de-transaction>`_ : liste des différentes catégories de boutons : soft, alcool, ...
|
||||
* `note/transaction/transaction <note#transaction>`_ : liste des transactions effectuées
|
||||
* `note/transaction/template <note#modele-de-transaction>`_ : liste des boutons enregistrés
|
||||
* `treasury/invoice <treasury#facture>`_ : liste des factures générées
|
||||
* `treasury/product <treasury#produit>`_ : liste des produits associés à des factures
|
||||
* `treasury/remittance_type <treasury#type-de-remise>`_ : liste des types de remises supportés : chèque
|
||||
* `treasury/remittance <treasury#remise>`_ : liste des différentes remises enregistrées
|
||||
* `treasury/remittance <treasury#remise>`_ : liste des crédits de la Société générale enregistrés
|
||||
* `permission/permission <permission#permission>`_ : liste de toutes les permissions enregistrées
|
||||
* `permission/roles <permission#permissions-par-roles>`_ : liste des permissions octroyées pour chacun des rôles
|
||||
* `logs <logs#journal-de-modification>`_ : liste des modifications enregistrées en base de données
|
||||
* `wei/club <wei#wei>`_ : liste des WEI
|
||||
* `wei/bus <wei#bus>`_ : liste des bus de tous les WEI
|
||||
* `wei/team <wei#equipe-de-bus>`_ : liste des équipes de tous les WEI
|
||||
* `wei/role <wei#role-au-wei>`_ : liste des rôles possibles pour le WEI
|
||||
* `wei/registration <wei#participation-au-wei>`_ : liste de toutes les inscriptions à un WEI
|
||||
* `wei/membership <wei#adhesion-au-wei>`_ : liste des adhésions compètes à un WEI
|
||||
|
||||
Utilisation de l'API
|
||||
--------------------
|
||||
@ -39,11 +59,23 @@ La page ``/api/<model>/`` affiche la liste de tous les éléments enregistrés.
|
||||
les attributs d'un objet uniquement.
|
||||
|
||||
L'affichage des données peut se faire sous deux formes : via une interface HTML propre ou directement en affichant
|
||||
le JSON brut. Le changement peut se faire en ajoutant en paramètre de l'URL ``format=json`` ou ``format=api`, ou bien
|
||||
en plaçant en en-tête de la requête ``Accept: application/json`` ou ``text/html``.
|
||||
le JSON brut. Le changement peut se faire en ajoutant en paramètre de l'URL ``format=json`` ou ``format=api``, ou bien
|
||||
en plaçant en en-tête de la requête ``Accept: application/json`` ou ``Accept: text/html``.
|
||||
|
||||
L'API Web propose des formulaires facilitant l'ajout et la modification d'éléments.
|
||||
|
||||
S'authentifier
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
L'authentification peut se faire soit par session en se connectant via la page de connexion classique,
|
||||
soit via un jeton d'authentification. Le jeton peut se récupérer via la page de son propre compte, en cliquant
|
||||
sur le bouton « `Accès API <https://note.crans.org/accounts/manage-auth-token/>`_ ». Il peut être révoqué et regénéré
|
||||
en un clic.
|
||||
|
||||
Pour s'authentifier via ce jeton, il faut ajouter l'en-tête ``Authorization: Token <TOKEN>`` aux paramètres HTTP.
|
||||
|
||||
En s'authentifiant par cette méthode, les masques de droit sont ignorés, les droits maximaux sont accordés.
|
||||
|
||||
GET
|
||||
~~~
|
||||
|
||||
@ -67,7 +99,19 @@ objets trouvés, au format JSON.
|
||||
Certaines pages disposent de filtres, permettant de sélectionner les objets recherchés. Par exemple, il est possible
|
||||
de chercher une note d'un certain type matchant avec un certain alias.
|
||||
|
||||
Le résultat est déjà par défaut filtré : seuls les éléments que l'utilisateur à le droit de voir sont affichés.
|
||||
Trois types de filtres sont implémentés :
|
||||
|
||||
* Les filtres Django, permettant d'ajouter ``?key=value`` dans l'URL pour filtrer les objets ayant ``value`` comme
|
||||
valeur pour la clé ``key`` ;
|
||||
* Les filtres de recherche, permettant une recherche plus souple notamment par expressions régulières ou contenance,
|
||||
et permet aussi de chercher parmi plusieurs clés à partir d'un champ ``search`` dans l'URL ;
|
||||
* Les filtres de tri, qui ne filtrent pas réellement mais changent l'ordre. En ajoutant ``?ordering=key`` dans l'URL,
|
||||
on trie les résultats selon la clé ``key`` dans l'ordre croissant, et ``?ordering=-key`` trie dans l'ordre
|
||||
décroissant.
|
||||
|
||||
Les filtres disponibles sont indiqués sur chacune des pages de documentation.
|
||||
|
||||
Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés.
|
||||
Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données.
|
||||
|
||||
Une requête à l'adresse ``/api/<model>/pk/`` affiche directement les informations du modèle demandé au format JSON.
|
42
docs/api/logs.rst
Normal file
42
docs/api/logs.rst
Normal file
@ -0,0 +1,42 @@
|
||||
API Logs
|
||||
========
|
||||
|
||||
Journal de modification
|
||||
-----------------------
|
||||
|
||||
**Chemin :** `/api/logs/ <https://note.crans.org/api/logs/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Changelog List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Changelog` objects, serialize it to JSON with the given serializer,\nthen render it on /api/logs/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
]
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``model``
|
||||
* ``action``
|
||||
* ``instance_pk``
|
||||
* ``user``
|
||||
* ``ip``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``timestamp``
|
||||
* ``id``
|
||||
|
476
docs/api/member.rst
Normal file
476
docs/api/member.rst
Normal file
@ -0,0 +1,476 @@
|
||||
API Membres
|
||||
===========
|
||||
|
||||
Profil utilisateur
|
||||
------------------
|
||||
|
||||
**Chemin :** `/api/members/profile/ <https://note.crans.org/api/members/profile/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Profile List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer,\nthen render it on /api/members/profile/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Num\u00e9ro de t\u00e9l\u00e9phone",
|
||||
"max_length": 50
|
||||
},
|
||||
"section": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Section",
|
||||
"help_text": "e.g. \"1A0\", \"9A\u2665\", \"SAPHIRE\"",
|
||||
"max_length": 255
|
||||
},
|
||||
"department": {
|
||||
"type": "choice",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "D\u00e9partement",
|
||||
"choices": [
|
||||
{
|
||||
"value": "A0",
|
||||
"display_name": "Informatique (A0)"
|
||||
},
|
||||
{
|
||||
"value": "A1",
|
||||
"display_name": "Math\u00e9matiques (A1)"
|
||||
},
|
||||
{
|
||||
"value": "A2",
|
||||
"display_name": "Chimie (A''2)"
|
||||
},
|
||||
{
|
||||
"value": "A'2",
|
||||
"display_name": "Physique appliqu\u00e9e (A'2)"
|
||||
},
|
||||
{
|
||||
"value": "A3",
|
||||
"display_name": "Biologie (A3)"
|
||||
},
|
||||
{
|
||||
"value": "B1234",
|
||||
"display_name": "SAPHIRE (B1234)"
|
||||
},
|
||||
{
|
||||
"value": "B1",
|
||||
"display_name": "M\u00e9canique (B1)"
|
||||
},
|
||||
{
|
||||
"value": "B2",
|
||||
"display_name": "G\u00e9nie civil (B2)"
|
||||
},
|
||||
{
|
||||
"value": "B3",
|
||||
"display_name": "G\u00e9nie m\u00e9canique (B3)"
|
||||
},
|
||||
{
|
||||
"value": "B4",
|
||||
"display_name": "EEA (B4)"
|
||||
},
|
||||
{
|
||||
"value": "C",
|
||||
"display_name": "Design (C)"
|
||||
},
|
||||
{
|
||||
"value": "D2",
|
||||
"display_name": "\u00c9conomie-gestion (D2)"
|
||||
},
|
||||
{
|
||||
"value": "D3",
|
||||
"display_name": "Sciences sociales (D3)"
|
||||
},
|
||||
{
|
||||
"value": "E",
|
||||
"display_name": "Anglais (E)"
|
||||
},
|
||||
{
|
||||
"value": "EXT",
|
||||
"display_name": "Externe (EXT)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"promotion": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Promotion",
|
||||
"help_text": "Ann\u00e9e d'entr\u00e9e dans l'\u00e9cole (None si non-\u00e9tudiant\u00b7e de l'ENS)",
|
||||
"min_value": 0,
|
||||
"max_value": 32767
|
||||
},
|
||||
"address": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Adresse",
|
||||
"max_length": 255
|
||||
},
|
||||
"paid": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Pay\u00e9",
|
||||
"help_text": "Indique si l'utilisateur per\u00e7oit un salaire."
|
||||
},
|
||||
"ml_events_registration": {
|
||||
"type": "choice",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "S'inscrire sur la liste de diffusion pour rester inform\u00e9 des \u00e9v\u00e9nements sur le campus (1 mail par semaine)",
|
||||
"choices": [
|
||||
{
|
||||
"value": "",
|
||||
"display_name": "Non"
|
||||
},
|
||||
{
|
||||
"value": "fr",
|
||||
"display_name": "Oui (les recevoir en fran\u00e7ais)"
|
||||
},
|
||||
{
|
||||
"value": "en",
|
||||
"display_name": "Oui (les recevoir en anglais)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ml_sport_registration": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "S'inscrire sur la liste de diffusion pour rester inform\u00e9 des actualit\u00e9s sportives sur le campus (1 mail par semaine)"
|
||||
},
|
||||
"ml_art_registration": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "S'inscrire sur la liste de diffusion pour rester inform\u00e9 des actualit\u00e9s artistiques sur le campus (1 mail par semaine)"
|
||||
},
|
||||
"report_frequency": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Fr\u00e9quence des rapports (en jours)",
|
||||
"min_value": 0,
|
||||
"max_value": 32767
|
||||
},
|
||||
"last_report": {
|
||||
"type": "datetime",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Date de dernier rapport"
|
||||
},
|
||||
"email_confirmed": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Adresse email confirm\u00e9e"
|
||||
},
|
||||
"registration_valid": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Inscription valide"
|
||||
},
|
||||
"user": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``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``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``user__first_name`` (expression régulière)
|
||||
* ``user__last_name`` (expression régulière)
|
||||
* ``user__username`` (expression régulière)
|
||||
* ``user__email`` (expression régulière)
|
||||
* ``user__note__alias__name`` (expression régulière)
|
||||
* ``user__note__alias__normalized_name`` (expression régulière)
|
||||
|
||||
Club
|
||||
----
|
||||
|
||||
**Chemin :** `/api/members/club/ <https://note.crans.org/api/members/club/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Club List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,\nthen render it on /api/members/club/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Courriel",
|
||||
"max_length": 254
|
||||
},
|
||||
"require_memberships": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "N\u00e9cessite des adh\u00e9sions",
|
||||
"help_text": "D\u00e9cochez si ce club n'utilise pas d'adh\u00e9sions."
|
||||
},
|
||||
"membership_fee_paid": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cotisation pour adh\u00e9rer (normalien \u00e9l\u00e8ve)",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_fee_unpaid": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cotisation pour adh\u00e9rer (normalien \u00e9tudiant)",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_duration": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Dur\u00e9e de l'adh\u00e9sion",
|
||||
"help_text": "La dur\u00e9e maximale (en jours) d'une adh\u00e9sion (NULL = infinie).",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_start": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "D\u00e9but de l'adh\u00e9sion",
|
||||
"help_text": "Date \u00e0 partir de laquelle les adh\u00e9rents peuvent renouveler leur adh\u00e9sion."
|
||||
},
|
||||
"membership_end": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Fin de l'adh\u00e9sion",
|
||||
"help_text": "Date maximale d'une fin d'adh\u00e9sion, apr\u00e8s laquelle les adh\u00e9rents doivent la renouveler."
|
||||
},
|
||||
"parent_club": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Club parent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``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``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``email`` (expression régulière)
|
||||
* ``note__alias__name`` (expression régulière)
|
||||
* ``note__alias__normalized_name`` (expression régulière)
|
||||
|
||||
Adhésion
|
||||
--------
|
||||
|
||||
**Chemin :** `/api/members/membership/ <https://note.crans.org/api/members/membership/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Membership List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,\nthen render it on /api/members/membership/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"date_start": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "L'adh\u00e9sion commence le"
|
||||
},
|
||||
"date_end": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "L'adh\u00e9sion finit le"
|
||||
},
|
||||
"fee": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Cotisation",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"user": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Utilisateur"
|
||||
},
|
||||
"club": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Club"
|
||||
},
|
||||
"roles": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "R\u00f4les"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``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``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``id``
|
||||
* ``date_start``
|
||||
* ``date_end``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``club__name`` (expression régulière)
|
||||
* ``club__email`` (expression régulière)
|
||||
* ``club__note__alias__name`` (expression régulière)
|
||||
* ``club__note__alias__normalized_name`` (expression régulière)
|
||||
* ``user__username`` (expression régulière)
|
||||
* ``user__last_name`` (expression régulière)
|
||||
* ``user__first_name`` (expression régulière)
|
||||
* ``user__email`` (expression régulière)
|
||||
* ``user__note__alias__name`` (expression régulière)
|
||||
* ``user__note__alias__normalized_name`` (expression régulière)
|
||||
* ``roles__name`` (expression régulière)
|
||||
|
403
docs/api/note.rst
Normal file
403
docs/api/note.rst
Normal file
@ -0,0 +1,403 @@
|
||||
API Note
|
||||
========
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
**Chemin :** `/api/note/note/ <https://note.crans.org/api/note/note/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Note Polymorphic List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Note` objects (with polymorhism),\nserialize it to JSON with the given serializer,\nthen render it on /api/note/note/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``alias__name``
|
||||
* ``polymorphic_ctype``
|
||||
* ``is_active``
|
||||
* ``balance``
|
||||
* ``last_negative``
|
||||
* ``created_at``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``alias__name``
|
||||
* ``alias__normalized_name``
|
||||
* ``balance``
|
||||
* ``created_at``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``alias__normalized_name`` (expression régulière)
|
||||
* ``alias__name`` (expression régulière)
|
||||
* ``polymorphic_ctype__model`` (expression régulière)
|
||||
* ``noteuser__user__last_name`` (expression régulière)
|
||||
* ``noteuser__user__first_name`` (expression régulière)
|
||||
* ``noteuser__user__email`` (expression régulière)
|
||||
* ``noteuser__user__email`` (expression régulière)
|
||||
* ``noteclub__club__email`` (expression régulière)
|
||||
|
||||
Alias
|
||||
-----
|
||||
|
||||
**Chemin :** `/api/note/alias/ <https://note.crans.org/api/note/alias/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Alias List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,\nthen render it on /api/aliases/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"normalized_name": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "Normalized name"
|
||||
},
|
||||
"note": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Note"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``note``
|
||||
* ``note__noteuser__user``
|
||||
* ``note__noteclub__club``
|
||||
* ``note__polymorphic_ctype__model``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``normalized_name``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``alias`` (cherche en priorité les alias les plus proches, puis cherche les alias normalisés)
|
||||
* ``normalized_name`` (expression régulière)
|
||||
* ``name`` (expression régulière)
|
||||
* ``note__polymorphic_ctype__model`` (expression régulière)
|
||||
|
||||
Consommateur
|
||||
------------
|
||||
|
||||
**Chemin :** `/api/note/consumer/ <https://note.crans.org/api/note/consumer/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Consumer List",
|
||||
"description": "",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
]
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Cette page est en lecture seule. Elle offre l'avantage de fournir directement les informations sur la note associée
|
||||
à l'alias au lieu de l'identifiant uniquement, afin de minimiser les appels à l'API.
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``alias`` (expression régulière, cherche en priorité les alias les plus proches, puis cherche les alias normalisés)
|
||||
* ``note``
|
||||
* ``note__noteuser__user``
|
||||
* ``note__noteclub__club``
|
||||
* ``note__polymorphic_ctype__model``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``normalized_name``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``normalized_name`` (expression régulière)
|
||||
* ``name`` (expression régulière)
|
||||
* ``note__polymorphic_ctype__model`` (expression régulière)
|
||||
|
||||
Catégorie de transaction
|
||||
------------------------
|
||||
|
||||
**Chemin :** `/api/note/transaction/category/ <https://note.crans.org/api/note/transaction/category/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Template Category List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `TemplateCategory` objects, serialize it to JSON with the given serializer,\nthen render it on /api/note/transaction/category/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 31
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``templates``
|
||||
* ``templates__name``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``templates__name`` (expression régulière)
|
||||
|
||||
Modèle de transaction
|
||||
---------------------
|
||||
|
||||
**Chemin :** `/api/note/transaction/template/ <https://note.crans.org/api/note/transaction/template/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Transaction Template List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,\nthen render it on /api/note/transaction/template/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Montant",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"display": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Afficher"
|
||||
},
|
||||
"highlighted": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Mis en avant"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Description",
|
||||
"max_length": 255
|
||||
},
|
||||
"destination": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Destination"
|
||||
},
|
||||
"category": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``amount``
|
||||
* ``display``
|
||||
* ``category``
|
||||
* ``category__name``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``amount``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``category__name`` (expression régulière)
|
||||
|
||||
Transaction
|
||||
-----------
|
||||
|
||||
**Chemin :** `/api/note/transaction/transaction/ <https://note.crans.org/api/note/transaction/transaction/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Transaction List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,\nthen render it on /api/note/transaction/transaction/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``source``
|
||||
* ``source_alias``
|
||||
* ``source__alias__name``
|
||||
* ``source__alias__normalized_name``
|
||||
* ``destination``
|
||||
* ``destination_alias``
|
||||
* ``destination__alias__name``
|
||||
* ``destination__alias__normalized_name``
|
||||
* ``quantity``
|
||||
* ``polymorphic_ctype``
|
||||
* ``amount``
|
||||
* ``created_at``
|
||||
* ``valid``
|
||||
* ``invalidity_reason``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``created_at``
|
||||
* ``amount``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``reason`` (expression régulière)
|
||||
* ``source_alias`` (expression régulière)
|
||||
* ``source__alias__name`` (expression régulière)
|
||||
* ``source__alias__normalized_name`` (expression régulière)
|
||||
* ``destination_alias`` (expression régulière)
|
||||
* ``destination__alias__name`` (expression régulière)
|
||||
* ``destination__alias__normalized_name`` (expression régulière)
|
||||
* ``invalidity_reason`` (expression régulière)
|
||||
|
82
docs/api/permission.rst
Normal file
82
docs/api/permission.rst
Normal file
@ -0,0 +1,82 @@
|
||||
API Permissions
|
||||
===============
|
||||
|
||||
Permission
|
||||
----------
|
||||
|
||||
**Chemin :** `/api/permission/permission/ <https://note.crans.org/api/permission/permission/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Permission List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Permission` objects, serialize it to JSON with the given serializer,\nthen render it on /api/permission/permission/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
]
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``model``
|
||||
* ``type``
|
||||
* ``query``
|
||||
* ``mask``
|
||||
* ``field``
|
||||
* ``permanent``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``model__name`` (expression régulière)
|
||||
* ``query`` (expression régulière)
|
||||
* ``description`` (expression régulière)
|
||||
|
||||
Permissions par rôles
|
||||
---------------------
|
||||
|
||||
**Chemin :** `/api/permission/roles/ <https://note.crans.org/api/permission/roles/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Role List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `RolePermission` objects, serialize it to JSON with the given serializer\nthen render it on /api/permission/roles/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
]
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``permissions``
|
||||
* ``for_club``
|
||||
* ``memberships__user``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``for_club__name`` (expression régulière)
|
||||
|
402
docs/api/treasury.rst
Normal file
402
docs/api/treasury.rst
Normal file
@ -0,0 +1,402 @@
|
||||
API Trésorerie
|
||||
==============
|
||||
|
||||
Facture
|
||||
-------
|
||||
|
||||
**Chemin :** `/api/treasury/invoice/ <https://note.crans.org/api/treasury/invoice/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Invoice List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,\nthen render it on /api/treasury/invoice/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Num\u00e9ro de facture",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"products": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "Products"
|
||||
},
|
||||
"bde": {
|
||||
"type": "choice",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "BDE"
|
||||
},
|
||||
"object": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Objet",
|
||||
"max_length": 255
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Description"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"address": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Adresse"
|
||||
},
|
||||
"date": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Date"
|
||||
},
|
||||
"acquitted": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Acquitt\u00e9e"
|
||||
},
|
||||
"locked": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Verrouill\u00e9e",
|
||||
"help_text": "Une facture ne peut plus \u00eatre modifi\u00e9e si elle est verrouill\u00e9e."
|
||||
},
|
||||
"tex": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Fichier TeX source"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``bde``
|
||||
* ``object``
|
||||
* ``description``
|
||||
* ``name``
|
||||
* ``address``
|
||||
* ``date``
|
||||
* ``acquitted``
|
||||
* ``locked``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``object`` (expression régulière)
|
||||
* ``description`` (expression régulière)
|
||||
* ``name`` (expression régulière)
|
||||
* ``address`` (expression régulière)
|
||||
|
||||
Produit
|
||||
-------
|
||||
|
||||
**Chemin :** `/api/treasury/product/ <https://note.crans.org/api/treasury/product/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Product List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,\nthen render it on /api/treasury/product/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"designation": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "D\u00e9signation",
|
||||
"max_length": 255
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Quantit\u00e9",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Prix unitaire",
|
||||
"min_value": -2147483648,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"invoice": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Facture"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``invoice``
|
||||
* ``designation``
|
||||
* ``quantity``
|
||||
* ``amount``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``designation`` (expression régulière)
|
||||
* ``invoice__object`` (expression régulière)
|
||||
|
||||
Type de remise
|
||||
--------------
|
||||
|
||||
**Chemin :** `/api/treasury/remittance_type/ <https://note.crans.org/api/treasury/remittance_type/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Remittance Type List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer\nthen render it on /api/treasury/remittance_type/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"note": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Note"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``note``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``note__special_type`` (expression régulière)
|
||||
|
||||
Remise
|
||||
------
|
||||
|
||||
**Chemin :** `/api/treasury/remittance/ <https://note.crans.org/api/treasury/remittance/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Remittance List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,\nthen render it on /api/treasury/remittance/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"transactions": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "Transactions"
|
||||
},
|
||||
"date": {
|
||||
"type": "datetime",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Date"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Commentaire",
|
||||
"max_length": 255
|
||||
},
|
||||
"closed": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Ferm\u00e9e"
|
||||
},
|
||||
"remittance_type": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``date``
|
||||
* ``remittance_type``
|
||||
* ``comment``
|
||||
* ``closed``
|
||||
* ``transaction_proxies__transaction``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``remittance_type__note__special_type`` (expression régulière)
|
||||
* ``comment`` (expression régulière)
|
||||
|
||||
Crédit de la société générale
|
||||
-----------------------------
|
||||
|
||||
**Chemin :** `/api/treasury/soge_credit/ <https://note.crans.org/api/treasury/soge_credit/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Soge Credit List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,\nthen render it on /api/treasury/soge_credit/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"user": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Utilisateur"
|
||||
},
|
||||
"credit_transaction": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Transaction de cr\u00e9dit"
|
||||
},
|
||||
"transactions": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Transactions d'adh\u00e9sion"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``user``
|
||||
* ``user__last_name``
|
||||
* ``user__first_name``
|
||||
* ``user__email``
|
||||
* ``user__note__alias__name``
|
||||
* ``user__note__alias__normalized_name``
|
||||
* ``transactions``
|
||||
* ``credit_transaction``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``user__last_name`` (expression régulière)
|
||||
* ``user__first_name`` (expression régulière)
|
||||
* ``user__email`` (expression régulière)
|
||||
* ``user__note__alias__name`` (expression régulière)
|
||||
* ``user__note__alias__normalized_name`` (expression régulière)
|
||||
|
710
docs/api/wei.rst
Normal file
710
docs/api/wei.rst
Normal file
@ -0,0 +1,710 @@
|
||||
API WEI
|
||||
=======
|
||||
|
||||
Wei
|
||||
---
|
||||
|
||||
**Chemin :** `/api/wei/club/ <https://note.crans.org/api/wei/club/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Wei Club List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/club/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Courriel",
|
||||
"max_length": 254
|
||||
},
|
||||
"require_memberships": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "N\u00e9cessite des adh\u00e9sions",
|
||||
"help_text": "D\u00e9cochez si ce club n'utilise pas d'adh\u00e9sions."
|
||||
},
|
||||
"membership_fee_paid": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cotisation pour adh\u00e9rer (normalien \u00e9l\u00e8ve)",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_fee_unpaid": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cotisation pour adh\u00e9rer (normalien \u00e9tudiant)",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_duration": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Dur\u00e9e de l'adh\u00e9sion",
|
||||
"help_text": "La dur\u00e9e maximale (en jours) d'une adh\u00e9sion (NULL = infinie).",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"membership_start": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "D\u00e9but de l'adh\u00e9sion",
|
||||
"help_text": "Date \u00e0 partir de laquelle les adh\u00e9rents peuvent renouveler leur adh\u00e9sion."
|
||||
},
|
||||
"membership_end": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Fin de l'adh\u00e9sion",
|
||||
"help_text": "Date maximale d'une fin d'adh\u00e9sion, apr\u00e8s laquelle les adh\u00e9rents doivent la renouveler."
|
||||
},
|
||||
"year": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Ann\u00e9e",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"date_start": {
|
||||
"type": "date",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "D\u00e9but"
|
||||
},
|
||||
"date_end": {
|
||||
"type": "date",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Fin"
|
||||
},
|
||||
"parent_club": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Club parent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``year``
|
||||
* ``date_start``
|
||||
* ``date_end``
|
||||
* ``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``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``email`` (expression régulière)
|
||||
* ``note__alias__name`` (expression régulière)
|
||||
* ``note__alias__normalized_name`` (expression régulière)
|
||||
|
||||
Bus
|
||||
---
|
||||
|
||||
**Chemin :** `/api/wei/bus/ <https://note.crans.org/api/wei/bus/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Bus List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/bus/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Description"
|
||||
},
|
||||
"information_json": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Informations sur le questionnaire",
|
||||
"help_text": "Informations sur le sondage pour les nouveaux membres, encod\u00e9es en JSON"
|
||||
},
|
||||
"wei": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "WEI"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``wei``
|
||||
* ``description``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``wei__name`` (expression régulière)
|
||||
* ``description`` (expression régulière)
|
||||
|
||||
Équipe de bus
|
||||
-------------
|
||||
|
||||
**Chemin :** `/api/wei/team/ <https://note.crans.org/api/wei/team/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Bus Team List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/team/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"color": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Couleur",
|
||||
"help_text": "La couleur du T-Shirt, stock\u00e9 sous la forme de son \u00e9quivalent num\u00e9rique",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Description"
|
||||
},
|
||||
"bus": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Bus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``bus``
|
||||
* ``color``
|
||||
* ``description``
|
||||
* ``bus__wei``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
* ``bus__name`` (expression régulière)
|
||||
* ``bus__wei__name`` (expression régulière)
|
||||
* ``description`` (expression régulière)
|
||||
|
||||
Rôle au wei
|
||||
-----------
|
||||
|
||||
**Chemin :** `/api/wei/role/ <https://note.crans.org/api/wei/role/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Wei Role List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/role/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom",
|
||||
"max_length": 255
|
||||
},
|
||||
"for_club": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "S'applique au club"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Permissions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``name``
|
||||
* ``permissions``
|
||||
* ``memberships``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``name`` (expression régulière)
|
||||
|
||||
Participant au wei
|
||||
------------------
|
||||
|
||||
**Chemin :** `/api/wei/registration/ <https://note.crans.org/api/wei/registration/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Wei Registration List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/registration/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"soge_credit": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Cr\u00e9dit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale"
|
||||
},
|
||||
"caution_check": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Ch\u00e8que de caution donn\u00e9"
|
||||
},
|
||||
"birth_date": {
|
||||
"type": "date",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Date de naissance"
|
||||
},
|
||||
"gender": {
|
||||
"type": "choice",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Genre",
|
||||
"choices": [
|
||||
{
|
||||
"value": "male",
|
||||
"display_name": "Homme"
|
||||
},
|
||||
{
|
||||
"value": "female",
|
||||
"display_name": "Femme"
|
||||
},
|
||||
{
|
||||
"value": "nonbinary",
|
||||
"display_name": "Non-binaire"
|
||||
}
|
||||
]
|
||||
},
|
||||
"clothing_cut": {
|
||||
"type": "choice",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Coupe de v\u00eatement",
|
||||
"choices": [
|
||||
{
|
||||
"value": "male",
|
||||
"display_name": "Homme"
|
||||
},
|
||||
{
|
||||
"value": "female",
|
||||
"display_name": "Femme"
|
||||
}
|
||||
]
|
||||
},
|
||||
"clothing_size": {
|
||||
"type": "choice",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Taille de v\u00eatement",
|
||||
"choices": [
|
||||
{
|
||||
"value": "XS",
|
||||
"display_name": "XS"
|
||||
},
|
||||
{
|
||||
"value": "S",
|
||||
"display_name": "S"
|
||||
},
|
||||
{
|
||||
"value": "M",
|
||||
"display_name": "M"
|
||||
},
|
||||
{
|
||||
"value": "L",
|
||||
"display_name": "L"
|
||||
},
|
||||
{
|
||||
"value": "XL",
|
||||
"display_name": "XL"
|
||||
},
|
||||
{
|
||||
"value": "XXL",
|
||||
"display_name": "XXL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"health_issues": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Probl\u00e8mes de sant\u00e9"
|
||||
},
|
||||
"emergency_contact_name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Nom du contact en cas d'urgence",
|
||||
"max_length": 255
|
||||
},
|
||||
"emergency_contact_phone": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "T\u00e9l\u00e9phone du contact en cas d'urgence",
|
||||
"max_length": 32
|
||||
},
|
||||
"first_year": {
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Premi\u00e8re ann\u00e9e",
|
||||
"help_text": "Indique si l'utilisateur est nouveau dans l'\u00e9cole."
|
||||
},
|
||||
"information_json": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Informations sur l'inscription",
|
||||
"help_text": "Informations sur l'inscription (bus pour les 2A+, questionnaire pour les 1A), encod\u00e9es en JSON"
|
||||
},
|
||||
"user": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Utilisateur"
|
||||
},
|
||||
"wei": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "WEI"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``user``
|
||||
* ``user__username``
|
||||
* ``user__first_name``
|
||||
* ``user__last_name``
|
||||
* ``user__email``
|
||||
* ``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``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``user__username`` (expression régulière)
|
||||
* ``user__first_name`` (expression régulière)
|
||||
* ``user__last_name`` (expression régulière)
|
||||
* ``user__email`` (expression régulière)
|
||||
* ``user__note__alias__name`` (expression régulière)
|
||||
* ``user__note__alias__normalized_name`` (expression régulière)
|
||||
* ``wei__name`` (expression régulière)
|
||||
* ``wei__email`` (expression régulière)
|
||||
* ``health_issues`` (expression régulière)
|
||||
* ``emergency_contact_name`` (expression régulière)
|
||||
* ``emergency_contact_phone`` (expression régulière)
|
||||
|
||||
Adhésion au wei
|
||||
---------------
|
||||
|
||||
**Chemin :** `/api/wei/membership/ <https://note.crans.org/api/wei/membership/>`_
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "Wei Membership List",
|
||||
"description": "REST API View set.\nThe djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,\nthen render it on /api/wei/membership/",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"read_only": true,
|
||||
"label": "ID"
|
||||
},
|
||||
"date_start": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "L'adh\u00e9sion commence le"
|
||||
},
|
||||
"date_end": {
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "L'adh\u00e9sion finit le"
|
||||
},
|
||||
"fee": {
|
||||
"type": "integer",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Cotisation",
|
||||
"min_value": 0,
|
||||
"max_value": 2147483647
|
||||
},
|
||||
"user": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Utilisateur"
|
||||
},
|
||||
"club": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "Club"
|
||||
},
|
||||
"bus": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Bus"
|
||||
},
|
||||
"team": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "\u00c9quipe"
|
||||
},
|
||||
"registration": {
|
||||
"type": "field",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "Inscription au WEI"
|
||||
},
|
||||
"roles": {
|
||||
"type": "field",
|
||||
"required": true,
|
||||
"read_only": false,
|
||||
"label": "R\u00f4les"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filtres Django
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* ``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``
|
||||
* ``bus``
|
||||
* ``bus__name``
|
||||
* ``team``
|
||||
* ``team__name``
|
||||
* ``registration``
|
||||
|
||||
Tris possible
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* ``id``
|
||||
* ``date_start``
|
||||
* ``date_end``
|
||||
|
||||
Filtres de recherche
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``club__name`` (expression régulière)
|
||||
* ``club__email`` (expression régulière)
|
||||
* ``club__note__alias__name`` (expression régulière)
|
||||
* ``club__note__alias__normalized_name`` (expression régulière)
|
||||
* ``user__username`` (expression régulière)
|
||||
* ``user__last_name`` (expression régulière)
|
||||
* ``user__first_name`` (expression régulière)
|
||||
* ``user__email`` (expression régulière)
|
||||
* ``user__note__alias__name`` (expression régulière)
|
||||
* ``user__note__alias__normalized_name`` (expression régulière)
|
||||
* ``roles__name`` (expression régulière)
|
||||
* ``bus__name`` (expression régulière)
|
||||
* ``team__name`` (expression régulière)
|
||||
|
@ -9,15 +9,17 @@ Applications de la NoteKfet2020
|
||||
note/index
|
||||
activity
|
||||
permission
|
||||
api
|
||||
../api/index
|
||||
registration
|
||||
logs
|
||||
treasury
|
||||
wei
|
||||
|
||||
La NoteKfet est un projet Django, décomposé en applications.
|
||||
Certaines Applications sont développées uniquement pour ce projet, et sont indispensables, d'autre sont packagé et sont installées comme dépendances.
|
||||
Enfin Des fonctionnalités annexes ont été rajouté, mais ne sont pas essentiel au déploiement de la NoteKfet; leur usage est cependant recommandé.
|
||||
Certaines Applications sont développées uniquement pour ce projet, et sont indispensables,
|
||||
d'autres sont packagesé et sont installées comme dépendances.
|
||||
Enfin des fonctionnalités annexes ont été rajouté, mais ne sont pas essentiel au déploiement de la NoteKfet;
|
||||
leur usage est cependant recommandé.
|
||||
|
||||
Le front utilise le framework Bootstrap4 et quelques morceaux de javascript custom.
|
||||
|
||||
@ -34,7 +36,7 @@ Applications indispensables
|
||||
La gestion des Activités (créations, gestion, entrée...)
|
||||
* `Permission <permission>`_ :
|
||||
Backend de droits, limites les pouvoirs des utilisateurs
|
||||
* `API <api>`_ :
|
||||
* `API <../api>`_ :
|
||||
API REST de la note, est notamment utilisée pour rendre la note dynamique
|
||||
(notamment la page de conso)
|
||||
* `Registration <registration>`_ :
|
||||
@ -54,7 +56,7 @@ Applications packagées
|
||||
* ``django_tables2``
|
||||
utiliser pour afficher des tables de données et les formater, en python plutôt qu'en HTML.
|
||||
* ``restframework``
|
||||
Base de l'`API <api>`_.
|
||||
Base de l'`API <../api>`_.
|
||||
|
||||
Applications facultatives
|
||||
-------------------------
|
||||
|
@ -142,3 +142,17 @@ class TurbolinksMiddleware(object):
|
||||
location = request.session.pop('_turbolinks_redirect_to')
|
||||
response['Turbolinks-Location'] = location
|
||||
return response
|
||||
|
||||
|
||||
class ClacksMiddleware(object):
|
||||
"""
|
||||
Add Clacks Overhead header on each response.
|
||||
See https://www.gnuterrypratchett.com/
|
||||
"""
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
response['X-Clacks-Overhead'] = 'GNU Terry Pratchett'
|
||||
return response
|
||||
|
@ -49,6 +49,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.forms',
|
||||
'django_filters',
|
||||
'django_extensions',
|
||||
|
||||
# API
|
||||
'rest_framework',
|
||||
@ -82,6 +83,7 @@ MIDDLEWARE = [
|
||||
'note_kfet.middlewares.SessionMiddleware',
|
||||
'note_kfet.middlewares.LoginByIPMiddleware',
|
||||
'note_kfet.middlewares.TurbolinksMiddleware',
|
||||
'note_kfet.middlewares.ClacksMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'note_kfet.urls'
|
||||
|
@ -1,12 +1,13 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# CAS
|
||||
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 = (
|
||||
('Note Kfet', 'notekfet@example.com'),
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* You should never see this file.
|
||||
* If you are not using the main language, 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.
|
||||
*/
|
||||
@ -9,14 +9,7 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
django.pluralidx = function(count) { return (count == 1) ? 0 : 1; };
|
||||
|
||||
|
||||
/* gettext library */
|
||||
@ -73,39 +66,41 @@
|
||||
/* formatting library */
|
||||
|
||||
django.formats = {
|
||||
"DATETIME_FORMAT": "j \\d\\e F \\d\\e Y \\a \\l\\a\\s H:i",
|
||||
"DATETIME_FORMAT": "N j, Y, P",
|
||||
"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"
|
||||
"%Y-%m-%d",
|
||||
"%m/%d/%Y %H:%M:%S",
|
||||
"%m/%d/%Y %H:%M:%S.%f",
|
||||
"%m/%d/%Y %H:%M",
|
||||
"%m/%d/%Y",
|
||||
"%m/%d/%y %H:%M:%S",
|
||||
"%m/%d/%y %H:%M:%S.%f",
|
||||
"%m/%d/%y %H:%M",
|
||||
"%m/%d/%y"
|
||||
],
|
||||
"DATE_FORMAT": "j \\d\\e F \\d\\e Y",
|
||||
"DATE_FORMAT": "N j, Y",
|
||||
"DATE_INPUT_FORMATS": [
|
||||
"%d/%m/%Y",
|
||||
"%d/%m/%y",
|
||||
"%Y-%m-%d"
|
||||
"%Y-%m-%d",
|
||||
"%m/%d/%Y",
|
||||
"%m/%d/%y"
|
||||
],
|
||||
"DECIMAL_SEPARATOR": ",",
|
||||
"FIRST_DAY_OF_WEEK": 1,
|
||||
"MONTH_DAY_FORMAT": "j \\d\\e F",
|
||||
"DECIMAL_SEPARATOR": ".",
|
||||
"FIRST_DAY_OF_WEEK": 0,
|
||||
"MONTH_DAY_FORMAT": "F j",
|
||||
"NUMBER_GROUPING": 3,
|
||||
"SHORT_DATETIME_FORMAT": "d/m/Y H:i",
|
||||
"SHORT_DATE_FORMAT": "d/m/Y",
|
||||
"THOUSAND_SEPARATOR": ".",
|
||||
"TIME_FORMAT": "H:i",
|
||||
"SHORT_DATETIME_FORMAT": "m/d/Y P",
|
||||
"SHORT_DATE_FORMAT": "m/d/Y",
|
||||
"THOUSAND_SEPARATOR": ",",
|
||||
"TIME_FORMAT": "P",
|
||||
"TIME_INPUT_FORMATS": [
|
||||
"%H:%M:%S",
|
||||
"%H:%M:%S.%f",
|
||||
"%H:%M"
|
||||
],
|
||||
"YEAR_MONTH_FORMAT": "F \\d\\e Y"
|
||||
"YEAR_MONTH_FORMAT": "F Y"
|
||||
};
|
||||
|
||||
django.get_format = function(format_type) {
|
||||
|
134
note_kfet/static/js/jsi18n/en.js
Normal file
134
note_kfet/static/js/jsi18n/en.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));
|
||||
|
@ -39,7 +39,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<script src="{% static "js/konami.js" %}"></script>
|
||||
|
||||
{# Translation in javascript files #}
|
||||
<script src="{% static "js/jsi18n/jsi18n."|add:LANGUAGE_CODE|add:".js" %}"></script>
|
||||
<script src="{% static "js/jsi18n/"|add:LANGUAGE_CODE|add:".js" %}"></script>
|
||||
|
||||
{# If extra ressources are needed for a form, load here #}
|
||||
{% if form.media %}
|
||||
|
5
tox.ini
5
tox.ini
@ -6,6 +6,9 @@ envlist =
|
||||
# Ubuntu 20.04 Python
|
||||
py38-django22
|
||||
|
||||
# Debian Bullseye Python
|
||||
py39-django22
|
||||
|
||||
linters
|
||||
skipsdist = True
|
||||
|
||||
@ -15,7 +18,7 @@ deps =
|
||||
-r{toxinidir}/requirements.txt
|
||||
coverage
|
||||
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
|
||||
|
||||
[testenv:linters]
|
||||
|
Reference in New Issue
Block a user