mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'consos' into rights
# Conflicts: # apps/logs/signals.py # note_kfet/settings/base.py
This commit is contained in:
commit
112d4b6c5a
|
@ -0,0 +1,13 @@
|
||||||
|
DJANGO_APP_STAGE="dev"
|
||||||
|
# Only used in dev mode, change to "postgresql" if you want to use PostgreSQL in dev
|
||||||
|
DJANGO_DEV_STORE_METHOD="sqllite"
|
||||||
|
DJANGO_DB_HOST="localhost"
|
||||||
|
DJANGO_DB_NAME="note_db"
|
||||||
|
DJANGO_DB_USER="note"
|
||||||
|
DJANGO_DB_PASSWORD="CHANGE_ME"
|
||||||
|
DJANGO_DB_PORT=""
|
||||||
|
DJANGO_SECRET_KEY="CHANGE_ME"
|
||||||
|
DJANGO_SETTINGS_MODULE="note_kfet.settings"
|
||||||
|
DOMAIN="localhost"
|
||||||
|
CONTACT_EMAIL="tresorerie.bde@localhost"
|
||||||
|
NOTE_URL="localhost"
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "apps/scripts"]
|
||||||
|
path = apps/scripts
|
||||||
|
url = git@gitlab.crans.org:bde/nk20-scripts.git
|
|
@ -9,10 +9,13 @@ RUN apt update && \
|
||||||
apt install -y gettext nginx uwsgi uwsgi-plugin-python3 && \
|
apt install -y gettext nginx uwsgi uwsgi-plugin-python3 && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY requirements.txt /code/
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
COPY . /code/
|
COPY . /code/
|
||||||
|
|
||||||
|
# Comment what is not needed
|
||||||
|
RUN pip install -r requirements/base.txt
|
||||||
|
RUN pip install -r requirements/api.txt
|
||||||
|
RUN pip install -r requirements/cas.txt
|
||||||
|
RUN pip install -r requirements/production.txt
|
||||||
|
|
||||||
ENTRYPOINT ["/code/entrypoint.sh"]
|
ENTRYPOINT ["/code/entrypoint.sh"]
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
61
README.md
61
README.md
|
@ -31,7 +31,7 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
|
||||||
|
|
||||||
$ python3 -m venv env
|
$ python3 -m venv env
|
||||||
$ source env/bin/activate
|
$ source env/bin/activate
|
||||||
(env)$ pip3 install -r requirements.txt
|
(env)$ pip3 install -r requirements/base.txt
|
||||||
(env)$ deactivate
|
(env)$ deactivate
|
||||||
|
|
||||||
4. uwsgi et Nginx
|
4. uwsgi et Nginx
|
||||||
|
@ -40,14 +40,13 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
|
||||||
|
|
||||||
$ cp nginx_note.conf_example nginx_note.conf
|
$ cp nginx_note.conf_example nginx_note.conf
|
||||||
|
|
||||||
***Modifier le fichier pour être en accord avec le reste de votre config***
|
***Modifier le fichier pour être en accord avec le reste de votre config***
|
||||||
|
|
||||||
On utilise uwsgi et Nginx pour gérer le coté serveu :
|
On utilise uwsgi et Nginx pour gérer le coté serveur :
|
||||||
|
|
||||||
$ sudo ln -sf /var/www/note_kfet/nginx_note.conf /etc/nginx/sites-enabled/
|
$ sudo ln -sf /var/www/note_kfet/nginx_note.conf /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
Si l'on a un emperor (plusieurs instance uwsgi):
|
||||||
Si l'on a un emperor (plusieurs instance uwsgi):
|
|
||||||
|
|
||||||
$ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/sites/
|
$ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/sites/
|
||||||
|
|
||||||
|
@ -85,7 +84,7 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
|
||||||
postgres=# CREATE DATABASE note_db OWNER note;
|
postgres=# CREATE DATABASE note_db OWNER note;
|
||||||
CREATE DATABASE
|
CREATE DATABASE
|
||||||
|
|
||||||
Si tout va bien:
|
Si tout va bien :
|
||||||
|
|
||||||
postgres=#\list
|
postgres=#\list
|
||||||
List of databases
|
List of databases
|
||||||
|
@ -96,22 +95,29 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
|
||||||
template0 | postgres | UTF8 | fr_FR.UTF-8 | fr_FR.UTF-8 | =c/postgres+postgres=CTc/postgres
|
template0 | postgres | UTF8 | fr_FR.UTF-8 | fr_FR.UTF-8 | =c/postgres+postgres=CTc/postgres
|
||||||
template1 | postgres | UTF8 | fr_FR.UTF-8 | fr_FR.UTF-8 | =c/postgres +postgres=CTc/postgres
|
template1 | postgres | UTF8 | fr_FR.UTF-8 | fr_FR.UTF-8 | =c/postgres +postgres=CTc/postgres
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
Dans un fichier `.env` à la racine du projet on renseigne des secrets:
|
|
||||||
|
|
||||||
DJANGO_APP_STAGE='prod'
|
|
||||||
DJANGO_DB_PASSWORD='le_mot_de_passe_de_la_bdd'
|
|
||||||
DJANGO_SECRET_KEY='une_secret_key_longue_et_compliquee'
|
|
||||||
ALLOWED_HOSTS='le_ndd_de_votre_instance'
|
|
||||||
|
|
||||||
|
|
||||||
6. Variable d'environnement et Migrations
|
6. Variable d'environnement et Migrations
|
||||||
|
|
||||||
|
On copie le fichier `.env_example` vers le fichier `.env` à la racine du projet
|
||||||
|
et on renseigne des secrets et des paramètres :
|
||||||
|
|
||||||
|
DJANGO_APP_STAGE="dev"
|
||||||
|
DJANGO_DEV_STORE_METHOD="sqllite"
|
||||||
|
DJANGO_DB_HOST="localhost"
|
||||||
|
DJANGO_DB_NAME="note_db"
|
||||||
|
DJANGO_DB_USER="note"
|
||||||
|
DJANGO_DB_PASSWORD="CHANGE_ME"
|
||||||
|
DJANGO_DB_PORT=""
|
||||||
|
DJANGO_SECRET_KEY="CHANGE_ME"
|
||||||
|
DJANGO_SETTINGS_MODULE="note_kfet.settings"
|
||||||
|
DOMAIN="localhost"
|
||||||
|
CONTACT_EMAIL="tresorerie.bde@localhost"
|
||||||
|
NOTE_URL="localhost"
|
||||||
|
|
||||||
Ensuite on (re)bascule dans l'environement virtuel et on lance les migrations
|
Ensuite on (re)bascule dans l'environement virtuel et on lance les migrations
|
||||||
|
|
||||||
$ source /env/bin/activate
|
$ source /env/bin/activate
|
||||||
(env)$ ./manage.py check # pas de bétise qui traine
|
(env)$ ./manage.py check # pas de bêtise qui traine
|
||||||
(env)$ ./manage.py makemigrations
|
(env)$ ./manage.py makemigrations
|
||||||
(env)$ ./manage.py migrate
|
(env)$ ./manage.py migrate
|
||||||
|
|
||||||
|
@ -126,17 +132,21 @@ Il est possible de travailler sur une instance Docker.
|
||||||
|
|
||||||
$ git clone git@gitlab.crans.org:bde/nk20.git
|
$ git clone git@gitlab.crans.org:bde/nk20.git
|
||||||
|
|
||||||
2. Dans le fichier `docker_compose.yml`, qu'on suppose déjà configuré,
|
2. Copiez le fichier `.env_example` à la racine du projet vers le fichier `.env`,
|
||||||
|
et mettez à jour vos variables d'environnement
|
||||||
|
|
||||||
|
3. Dans le fichier `docker_compose.yml`, qu'on suppose déjà configuré,
|
||||||
ajouter les lignes suivantes, en les adaptant à la configuration voulue :
|
ajouter les lignes suivantes, en les adaptant à la configuration voulue :
|
||||||
|
|
||||||
nk20:
|
nk20:
|
||||||
build: /chemin/vers/nk20
|
build: /chemin/vers/nk20
|
||||||
volumes:
|
volumes:
|
||||||
- /chemin/vers/nk20:/code/
|
- /chemin/vers/nk20:/code/
|
||||||
|
env_file: /chemin/vers/nk20/.env
|
||||||
restart: always
|
restart: always
|
||||||
labels:
|
labels:
|
||||||
- traefik.domain=ndd.exemple.com
|
- traefik.domain=ndd.example.com
|
||||||
- traefik.frontend.rule=Host:ndd.exemple.com
|
- traefik.frontend.rule=Host:ndd.example.com
|
||||||
- traefik.port=8000
|
- traefik.port=8000
|
||||||
|
|
||||||
3. Enjoy :
|
3. Enjoy :
|
||||||
|
@ -159,17 +169,20 @@ un serveur de développement par exemple sur son ordinateur.
|
||||||
$ source venv/bin/activate
|
$ source venv/bin/activate
|
||||||
(env)$ pip install -r requirements.txt
|
(env)$ pip install -r requirements.txt
|
||||||
|
|
||||||
3. Migrations et chargement des données initiales :
|
3. Copier le fichier `.env_example` vers `.env` à la racine du projet et mettre à jour
|
||||||
|
ce qu'il faut
|
||||||
|
|
||||||
|
4. Migrations et chargement des données initiales :
|
||||||
|
|
||||||
(env)$ ./manage.py makemigrations
|
(env)$ ./manage.py makemigrations
|
||||||
(env)$ ./manage.py migrate
|
(env)$ ./manage.py migrate
|
||||||
(env)$ ./manage.py loaddata initial
|
(env)$ ./manage.py loaddata initial
|
||||||
|
|
||||||
4. Créer un super-utilisateur :
|
5. Créer un super-utilisateur :
|
||||||
|
|
||||||
(env)$ ./manage.py createsuperuser
|
(env)$ ./manage.py createsuperuser
|
||||||
|
|
||||||
5. Enjoy :
|
6. Enjoy :
|
||||||
|
|
||||||
(env)$ ./manage.py runserver 0.0.0.0:8000
|
(env)$ ./manage.py runserver 0.0.0.0:8000
|
||||||
|
|
||||||
|
@ -184,4 +197,4 @@ Il est disponible [ici](https://wiki.crans.org/NoteKfet/NoteKfet2018/CdC).
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
La documentation est générée par django et son module admindocs.
|
La documentation est générée par django et son module admindocs.
|
||||||
**Commenter votre code !**
|
**Commentez votre code !**
|
||||||
|
|
|
@ -11,7 +11,7 @@ class ActivityAdmin(admin.ModelAdmin):
|
||||||
Admin customisation for Activity
|
Admin customisation for Activity
|
||||||
"""
|
"""
|
||||||
list_display = ('name', 'activity_type', 'organizer')
|
list_display = ('name', 'activity_type', 'organizer')
|
||||||
list_filter = ('activity_type', )
|
list_filter = ('activity_type',)
|
||||||
search_fields = ['name', 'organizer__name']
|
search_fields = ['name', 'organizer__name']
|
||||||
|
|
||||||
# Organize activities by start date
|
# Organize activities by start date
|
||||||
|
|
|
@ -11,6 +11,7 @@ class ActivityTypeSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Activity types.
|
REST API Serializer for Activity types.
|
||||||
The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ActivityType
|
model = ActivityType
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -21,6 +22,7 @@ class ActivitySerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Activities.
|
REST API Serializer for Activities.
|
||||||
The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Activity
|
model = Activity
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -31,6 +33,7 @@ class GuestSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Guests.
|
REST API Serializer for Guests.
|
||||||
The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Guest
|
model = Guest
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
|
|
||||||
from ..models import ActivityType, Activity, Guest
|
|
||||||
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
|
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
|
||||||
|
from ..models import ActivityType, Activity, Guest
|
||||||
|
|
||||||
|
|
||||||
class ActivityTypeViewSet(viewsets.ModelViewSet):
|
class ActivityTypeViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -15,6 +16,8 @@ class ActivityTypeViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = ActivityType.objects.all()
|
queryset = ActivityType.objects.all()
|
||||||
serializer_class = ActivityTypeSerializer
|
serializer_class = ActivityTypeSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['name', 'can_invite', ]
|
||||||
|
|
||||||
|
|
||||||
class ActivityViewSet(viewsets.ModelViewSet):
|
class ActivityViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -25,6 +28,8 @@ class ActivityViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Activity.objects.all()
|
queryset = Activity.objects.all()
|
||||||
serializer_class = ActivitySerializer
|
serializer_class = ActivitySerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['name', 'description', 'activity_type', ]
|
||||||
|
|
||||||
|
|
||||||
class GuestViewSet(viewsets.ModelViewSet):
|
class GuestViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -35,3 +40,5 @@ class GuestViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Guest.objects.all()
|
queryset = Guest.objects.all()
|
||||||
serializer_class = GuestSerializer
|
serializer_class = GuestSerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
|
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import routers, serializers, viewsets
|
from rest_framework import routers, serializers, viewsets
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
from activity.api.urls import register_activity_urls
|
from activity.api.urls import register_activity_urls
|
||||||
from member.api.urls import register_members_urls
|
from member.api.urls import register_members_urls
|
||||||
from note.api.urls import register_note_urls
|
from note.api.urls import register_note_urls
|
||||||
|
from logs.api.urls import register_logs_urls
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
@ -14,6 +18,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Users.
|
REST API Serializer for Users.
|
||||||
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
exclude = (
|
exclude = (
|
||||||
|
@ -23,6 +28,17 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Users.
|
||||||
|
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ContentType
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
|
@ -31,15 +47,30 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
|
||||||
|
search_fields = ['$username', '$first_name', '$last_name', ]
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeViewSet(viewsets.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/
|
||||||
|
"""
|
||||||
|
queryset = ContentType.objects.all()
|
||||||
|
serializer_class = ContentTypeSerializer
|
||||||
|
|
||||||
|
|
||||||
# Routers provide an easy way of automatically determining the URL conf.
|
# Routers provide an easy way of automatically determining the URL conf.
|
||||||
# Register each app API router and user viewset
|
# Register each app API router and user viewset
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
router.register('models', ContentTypeViewSet)
|
||||||
router.register('user', UserViewSet)
|
router.register('user', UserViewSet)
|
||||||
register_members_urls(router, 'members')
|
register_members_urls(router, 'members')
|
||||||
register_activity_urls(router, 'activity')
|
register_activity_urls(router, 'activity')
|
||||||
register_note_urls(router, 'note')
|
register_note_urls(router, 'note')
|
||||||
|
register_logs_urls(router, 'logs')
|
||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import Changelog
|
||||||
|
|
||||||
|
|
||||||
|
class ChangelogSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Changelog types.
|
||||||
|
The djangorestframework plugin will analyse the model `Changelog` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Changelog
|
||||||
|
fields = '__all__'
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
read_only_fields = [f.name for f in model._meta.get_fields()] # Changelogs are read-only protected
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from .views import ChangelogViewSet
|
||||||
|
|
||||||
|
|
||||||
|
def register_logs_urls(router, path):
|
||||||
|
"""
|
||||||
|
Configure router for Activity REST API.
|
||||||
|
"""
|
||||||
|
router.register(path, ChangelogViewSet)
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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 import viewsets
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
from .serializers import ChangelogSerializer
|
||||||
|
from ..models import Changelog
|
||||||
|
|
||||||
|
|
||||||
|
class ChangelogViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
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()
|
||||||
|
serializer_class = ChangelogSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
||||||
|
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
|
||||||
|
ordering_fields = ['timestamp', ]
|
||||||
|
ordering = ['-timestamp', ]
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import pre_save, post_save, post_delete
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,4 +12,7 @@ class LogsConfig(AppConfig):
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
import logs.signals
|
from . import signals
|
||||||
|
pre_save.connect(signals.pre_save_object)
|
||||||
|
post_save.connect(signals.save_object)
|
||||||
|
post_delete.connect(signals.delete_object)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
from threading import local
|
||||||
|
|
||||||
|
|
||||||
|
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
|
||||||
|
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
|
||||||
|
|
||||||
|
_thread_locals = local()
|
||||||
|
|
||||||
|
|
||||||
|
def _set_current_user_and_ip(user=None, ip=None):
|
||||||
|
setattr(_thread_locals, USER_ATTR_NAME, user)
|
||||||
|
setattr(_thread_locals, IP_ATTR_NAME, ip)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_user():
|
||||||
|
return getattr(_thread_locals, USER_ATTR_NAME, None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_ip():
|
||||||
|
return getattr(_thread_locals, IP_ATTR_NAME, None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_authenticated_user():
|
||||||
|
current_user = get_current_user()
|
||||||
|
if isinstance(current_user, AnonymousUser):
|
||||||
|
return None
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
class LogsMiddleware(object):
|
||||||
|
"""
|
||||||
|
This middleware get the current user with his or her IP address on each request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
user = request.user
|
||||||
|
if 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
|
||||||
|
_set_current_user_and_ip(user, ip)
|
||||||
|
response = self.get_response(request)
|
||||||
|
_set_current_user_and_ip(None, None)
|
||||||
|
|
||||||
|
return response
|
|
@ -1,16 +1,16 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Changelog(models.Model):
|
class Changelog(models.Model):
|
||||||
"""
|
"""
|
||||||
Store each modification on the database (except sessions and logging),
|
Store each modification in the database (except sessions and logging),
|
||||||
including creating, editing and deleting models.
|
including creating, editing and deleting models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -56,6 +56,12 @@ class Changelog(models.Model):
|
||||||
max_length=16,
|
max_length=16,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
|
choices=[
|
||||||
|
('create', _('create')),
|
||||||
|
('edit', _('edit')),
|
||||||
|
('delete', _('delete')),
|
||||||
|
],
|
||||||
|
default='edit',
|
||||||
verbose_name=_('action'),
|
verbose_name=_('action'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,66 +1,40 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import serializers
|
from rest_framework.renderers import JSONRenderer
|
||||||
from django.db.models.signals import pre_save, post_save, post_delete
|
from rest_framework.serializers import ModelSerializer
|
||||||
from django.dispatch import receiver
|
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
from note.models import NoteUser, Alias
|
||||||
|
|
||||||
|
from .middlewares import get_current_authenticated_user, get_current_ip
|
||||||
from .models import Changelog
|
from .models import Changelog
|
||||||
|
|
||||||
|
|
||||||
def get_request_in_signal(sender):
|
# Ces modèles ne nécessitent pas de logs
|
||||||
req = None
|
|
||||||
for entry in reversed(inspect.stack()):
|
|
||||||
try:
|
|
||||||
req = entry[0].f_locals['request']
|
|
||||||
# Check if there is a user
|
|
||||||
# noinspection PyStatementEffect
|
|
||||||
req.user
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not req:
|
|
||||||
print("WARNING: Attempt to save " + str(sender) + " with no user")
|
|
||||||
|
|
||||||
return req
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_and_ip(sender):
|
|
||||||
req = get_request_in_signal(sender)
|
|
||||||
try:
|
|
||||||
user = req.user
|
|
||||||
if 'HTTP_X_FORWARDED_FOR' in req.META:
|
|
||||||
ip = req.META.get('HTTP_X_FORWARDED_FOR')
|
|
||||||
else:
|
|
||||||
ip = req.META.get('REMOTE_ADDR')
|
|
||||||
except:
|
|
||||||
user = None
|
|
||||||
ip = None
|
|
||||||
return user, ip
|
|
||||||
|
|
||||||
|
|
||||||
EXCLUDED = [
|
EXCLUDED = [
|
||||||
'admin.logentry',
|
'admin.logentry',
|
||||||
'authtoken.token',
|
'authtoken.token',
|
||||||
'cas_server.user',
|
'cas_server.proxygrantingticket',
|
||||||
'cas_server.userattributes',
|
'cas_server.proxyticket',
|
||||||
'contenttypes.contenttype',
|
'cas_server.serviceticket',
|
||||||
'logs.changelog',
|
'cas_server.user',
|
||||||
'migrations.migration',
|
'cas_server.userattributes',
|
||||||
'note.noteuser',
|
'contenttypes.contenttype',
|
||||||
'note.noteclub',
|
'logs.changelog', # Never remove this line
|
||||||
'note.notespecial',
|
'migrations.migration',
|
||||||
'sessions.session',
|
'note.note' # We only store the subclasses
|
||||||
'reversion.revision',
|
'note.transaction',
|
||||||
'reversion.version',
|
'sessions.session',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save)
|
|
||||||
def pre_save_object(sender, instance, **kwargs):
|
def pre_save_object(sender, instance, **kwargs):
|
||||||
|
"""
|
||||||
|
Before a model get saved, we get the previous instance that is currently in the database
|
||||||
|
"""
|
||||||
qs = sender.objects.filter(pk=instance.pk).all()
|
qs = sender.objects.filter(pk=instance.pk).all()
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
instance._previous = qs.get()
|
instance._previous = qs.get()
|
||||||
|
@ -68,30 +42,51 @@ def pre_save_object(sender, instance, **kwargs):
|
||||||
instance._previous = None
|
instance._previous = None
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save)
|
|
||||||
def save_object(sender, instance, **kwargs):
|
def save_object(sender, instance, **kwargs):
|
||||||
|
"""
|
||||||
|
Each time a model is saved, an entry in the table `Changelog` is added in the database
|
||||||
|
in order to store each modification made
|
||||||
|
"""
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
previous = instance._previous
|
previous = instance._previous
|
||||||
|
|
||||||
user, ip = get_user_and_ip(sender)
|
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||||
|
user, ip = get_current_authenticated_user(), get_current_ip()
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
if user is None:
|
||||||
if isinstance(user, AnonymousUser):
|
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||||
user = None
|
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||||
|
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||||
|
ip = "127.0.0.1"
|
||||||
|
username = Alias.normalize(getpass.getuser())
|
||||||
|
note = NoteUser.objects.filter(alias__normalized_name=username)
|
||||||
|
# if not note.exists():
|
||||||
|
# print("WARNING: A model attempted to be saved in the DB, but the actor is unknown: " + username)
|
||||||
|
# else:
|
||||||
|
if note.exists():
|
||||||
|
user = note.get().user
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
if user is not None and instance._meta.label_lower == "auth.user" and previous:
|
if user is not None and instance._meta.label_lower == "auth.user" and previous:
|
||||||
# Don't save last login modifications
|
# On n'enregistre pas les connexions
|
||||||
if instance.last_login != previous.last_login:
|
if instance.last_login != previous.last_login:
|
||||||
return
|
return
|
||||||
|
|
||||||
previous_json = serializers.serialize('json', [previous, ])[1:-1] if previous else None
|
# On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
|
||||||
instance_json = serializers.serialize('json', [instance, ])[1:-1]
|
class CustomSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = instance.__class__
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
previous_json = JSONRenderer().render(CustomSerializer(previous).data).decode("UTF-8") if previous else None
|
||||||
|
instance_json = JSONRenderer().render(CustomSerializer(instance).data).decode("UTF-8")
|
||||||
|
|
||||||
if previous_json == instance_json:
|
if previous_json == instance_json:
|
||||||
# No modification
|
# Pas de log s'il n'y a pas de modification
|
||||||
return
|
return
|
||||||
|
|
||||||
Changelog.objects.create(user=user,
|
Changelog.objects.create(user=user,
|
||||||
|
@ -104,15 +99,38 @@ def save_object(sender, instance, **kwargs):
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete)
|
|
||||||
def delete_object(sender, instance, **kwargs):
|
def delete_object(sender, instance, **kwargs):
|
||||||
|
"""
|
||||||
|
Each time a model is deleted, an entry in the table `Changelog` is added in the database
|
||||||
|
"""
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
if instance._meta.label_lower in EXCLUDED:
|
if instance._meta.label_lower in EXCLUDED:
|
||||||
return
|
return
|
||||||
|
|
||||||
user, ip = get_user_and_ip(sender)
|
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||||
|
user, ip = get_current_authenticated_user(), get_current_ip()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||||
|
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||||
|
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||||
|
ip = "127.0.0.1"
|
||||||
|
username = Alias.normalize(getpass.getuser())
|
||||||
|
note = NoteUser.objects.filter(alias__normalized_name=username)
|
||||||
|
# if not note.exists():
|
||||||
|
# print("WARNING: A model attempted to be saved in the DB, but the actor is unknown: " + username)
|
||||||
|
# else:
|
||||||
|
if note.exists():
|
||||||
|
user = note.get().user
|
||||||
|
|
||||||
|
# On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
|
||||||
|
class CustomSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = instance.__class__
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
instance_json = JSONRenderer().render(CustomSerializer(instance).data).decode("UTF-8")
|
||||||
|
|
||||||
instance_json = serializers.serialize('json', [instance, ])[1:-1]
|
|
||||||
Changelog.objects.create(user=user,
|
Changelog.objects.create(user=user,
|
||||||
ip=ip,
|
ip=ip,
|
||||||
model=ContentType.objects.get_for_model(instance),
|
model=ContentType.objects.get_for_model(instance),
|
||||||
|
|
|
@ -18,9 +18,9 @@ class ProfileInline(admin.StackedInline):
|
||||||
|
|
||||||
|
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
inlines = (ProfileInline, )
|
inlines = (ProfileInline,)
|
||||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
||||||
list_select_related = ('profile', )
|
list_select_related = ('profile',)
|
||||||
form = ProfileForm
|
form = ProfileForm
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
|
|
|
@ -11,6 +11,7 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Profiles.
|
REST API Serializer for Profiles.
|
||||||
The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Profile
|
model = Profile
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -21,6 +22,7 @@ class ClubSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Clubs.
|
REST API Serializer for Clubs.
|
||||||
The djangorestframework plugin will analyse the model `Club` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Club` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Club
|
model = Club
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -31,6 +33,7 @@ class RoleSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Roles.
|
REST API Serializer for Roles.
|
||||||
The djangorestframework plugin will analyse the model `Role` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Role` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -41,6 +44,7 @@ class MembershipSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Memberships.
|
REST API Serializer for Memberships.
|
||||||
The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Membership
|
model = Membership
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
|
|
||||||
from ..models import Profile, Club, Role, Membership
|
|
||||||
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
|
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
|
||||||
|
from ..models import Profile, Club, Role, Membership
|
||||||
|
|
||||||
|
|
||||||
class ProfileViewSet(viewsets.ModelViewSet):
|
class ProfileViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -25,6 +26,8 @@ class ClubViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Club.objects.all()
|
queryset = Club.objects.all()
|
||||||
serializer_class = ClubSerializer
|
serializer_class = ClubSerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class RoleViewSet(viewsets.ModelViewSet):
|
class RoleViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -35,6 +38,8 @@ class RoleViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.all()
|
||||||
serializer_class = RoleSerializer
|
serializer_class = RoleSerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class MembershipViewSet(viewsets.ModelViewSet):
|
class MembershipViewSet(viewsets.ModelViewSet):
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters import FilterSet, CharFilter
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db.models import CharField
|
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Layout, Submit
|
from crispy_forms.layout import Layout, Submit
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django_filters import FilterSet, CharFilter
|
||||||
|
|
||||||
|
|
||||||
class UserFilter(FilterSet):
|
class UserFilter(FilterSet):
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from crispy_forms.bootstrap import Div
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from crispy_forms.layout import Layout
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
|
from django import forms
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django import forms
|
|
||||||
|
|
||||||
from .models import Profile, Club, Membership
|
from .models import Profile, Club, Membership
|
||||||
|
|
||||||
from crispy_forms.helper import FormHelper
|
|
||||||
from crispy_forms.bootstrap import Div
|
|
||||||
from crispy_forms.layout import Layout
|
|
||||||
|
|
||||||
|
|
||||||
class SignUpForm(UserCreationForm):
|
class SignUpForm(UserCreationForm):
|
||||||
def __init__(self,*args,**kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args,**kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['username'].widget.attrs.pop("autofocus", None)
|
self.fields['username'].widget.attrs.pop("autofocus", None)
|
||||||
self.fields['first_name'].widget.attrs.update({"autofocus":"autofocus"})
|
self.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
@ -28,6 +27,7 @@ class ProfileForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
A form for the extras field provided by the :model:`member.Profile` model.
|
A form for the extras field provided by the :model:`member.Profile` model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Profile
|
model = Profile
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -42,7 +42,7 @@ class ClubForm(forms.ModelForm):
|
||||||
|
|
||||||
class AddMembersForm(forms.Form):
|
class AddMembersForm(forms.Form):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('', )
|
fields = ('',)
|
||||||
|
|
||||||
|
|
||||||
class MembershipForm(forms.ModelForm):
|
class MembershipForm(forms.ModelForm):
|
||||||
|
@ -54,13 +54,13 @@ class MembershipForm(forms.ModelForm):
|
||||||
# et récupère les noms d'utilisateur valides
|
# et récupère les noms d'utilisateur valides
|
||||||
widgets = {
|
widgets = {
|
||||||
'user':
|
'user':
|
||||||
autocomplete.ModelSelect2(
|
autocomplete.ModelSelect2(
|
||||||
url='member:user_autocomplete',
|
url='member:user_autocomplete',
|
||||||
attrs={
|
attrs={
|
||||||
'data-placeholder': 'Nom ...',
|
'data-placeholder': 'Nom ...',
|
||||||
'data-minimum-input-length': 1,
|
'data-minimum-input-length': 1,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
|
@ -48,9 +48,10 @@ class Profile(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('user profile')
|
verbose_name = _('user profile')
|
||||||
verbose_name_plural = _('user profile')
|
verbose_name_plural = _('user profile')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('user_detail', args=(self.pk, ))
|
return reverse('user_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ class Club(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('member:club_detail', args=(self.pk, ))
|
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
|
@ -161,7 +162,7 @@ class Membership(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('membership')
|
verbose_name = _('membership')
|
||||||
verbose_name_plural = _('memberships')
|
verbose_name_plural = _('memberships')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
class RolePermissions(models.Model):
|
class RolePermissions(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
def save_user_profile(instance, created, raw, **_kwargs):
|
def save_user_profile(instance, created, raw, **_kwargs):
|
||||||
"""
|
"""
|
||||||
Hook to create and save a profile when an user is updated if it is not registered with the signup form
|
Hook to create and save a profile when an user is updated if it is not registered with the signup form
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView,DeleteView
|
|
||||||
from django.views.generic.edit import FormMixin
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.conf import settings
|
|
||||||
from django_tables2.views import SingleTableView
|
|
||||||
from rest_framework.authtoken.models import Token
|
|
||||||
from dal import autocomplete
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from dal import autocomplete
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView, DeleteView
|
||||||
|
from django.views.generic.edit import FormMixin
|
||||||
|
from django_tables2.views import SingleTableView
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
from note.forms import AliasForm, ImageForm
|
||||||
from note.models import Alias, NoteUser
|
from note.models import Alias, NoteUser
|
||||||
from note.models.transactions import Transaction
|
from note.models.transactions import Transaction
|
||||||
from note.tables import HistoryTable, AliasTable
|
from note.tables import HistoryTable, AliasTable
|
||||||
from note.forms import AliasForm, ImageForm
|
|
||||||
|
|
||||||
from .models import Profile, Club, Membership
|
|
||||||
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
|
|
||||||
from .tables import ClubTable, UserTable
|
|
||||||
from .filters import UserFilter, UserFilterFormHelper
|
from .filters import UserFilter, UserFilterFormHelper
|
||||||
|
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
|
||||||
|
from .models import Club, Membership
|
||||||
|
from .tables import ClubTable, UserTable
|
||||||
|
|
||||||
|
|
||||||
class UserCreateView(CreateView):
|
class UserCreateView(CreateView):
|
||||||
|
@ -49,10 +49,10 @@ class UserCreateView(CreateView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
profile_form = ProfileForm(self.request.POST)
|
profile_form = ProfileForm(self.request.POST)
|
||||||
if form.is_valid() and profile_form.is_valid():
|
if form.is_valid() and profile_form.is_valid():
|
||||||
user = form.save()
|
user = form.save(commit=False)
|
||||||
profile = profile_form.save(commit=False)
|
user.profile = profile_form.save(commit=False)
|
||||||
profile.user = user
|
user.save()
|
||||||
profile.save()
|
user.profile.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
return reverse_lazy('member:user_detail',
|
return reverse_lazy('member:user_detail',
|
||||||
kwargs={'pk': kwargs['id']})
|
kwargs={'pk': kwargs['id']})
|
||||||
else:
|
else:
|
||||||
return reverse_lazy('member:user_detail', args=(self.object.id, ))
|
return reverse_lazy('member:user_detail', args=(self.object.id,))
|
||||||
|
|
||||||
|
|
||||||
class UserDetailView(LoginRequiredMixin, DetailView):
|
class UserDetailView(LoginRequiredMixin, DetailView):
|
||||||
|
@ -124,7 +124,7 @@ class UserDetailView(LoginRequiredMixin, DetailView):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
user = context['user_object']
|
user = context['user_object']
|
||||||
history_list = \
|
history_list = \
|
||||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))
|
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note)).order_by("-id")
|
||||||
context['history_list'] = HistoryTable(history_list)
|
context['history_list'] = HistoryTable(history_list)
|
||||||
club_list = \
|
club_list = \
|
||||||
Membership.objects.all().filter(user=user).only("club")
|
Membership.objects.all().filter(user=user).only("club")
|
||||||
|
@ -157,13 +157,14 @@ class UserListView(LoginRequiredMixin, SingleTableView):
|
||||||
context["filter"] = self.filter
|
context["filter"] = self.filter
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class AliasView(LoginRequiredMixin,FormMixin,DetailView):
|
|
||||||
|
class AliasView(LoginRequiredMixin, FormMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'member/profile_alias.html'
|
template_name = 'member/profile_alias.html'
|
||||||
context_object_name = 'user_object'
|
context_object_name = 'user_object'
|
||||||
form_class = AliasForm
|
form_class = AliasForm
|
||||||
|
|
||||||
def get_context_data(self,**kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['user_object'].note
|
note = context['user_object'].note
|
||||||
context["aliases"] = AliasTable(note.alias_set.all())
|
context["aliases"] = AliasTable(note.alias_set.all())
|
||||||
|
@ -172,7 +173,7 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
|
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
|
||||||
|
|
||||||
def post(self,request,*args,**kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
@ -186,42 +187,45 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView):
|
||||||
alias.save()
|
alias.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class DeleteAliasView(LoginRequiredMixin, DeleteView):
|
class DeleteAliasView(LoginRequiredMixin, DeleteView):
|
||||||
model = Alias
|
model = Alias
|
||||||
|
|
||||||
def delete(self,request,*args,**kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
self.object.delete()
|
self.object.delete()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
# TODO: pass message to redirected view.
|
# TODO: pass message to redirected view.
|
||||||
messages.error(self.request,str(e))
|
messages.error(self.request, str(e))
|
||||||
else:
|
else:
|
||||||
messages.success(self.request,_("Alias successfully deleted"))
|
messages.success(self.request, _("Alias successfully deleted"))
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
print(self.request)
|
print(self.request)
|
||||||
return reverse_lazy('member:user_alias',kwargs={'pk':self.object.note.user.pk})
|
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.note.user.pk})
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return self.post(request, *args, **kwargs)
|
return self.post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'member/profile_picture_update.html'
|
template_name = 'member/profile_picture_update.html'
|
||||||
context_object_name = 'user_object'
|
context_object_name = 'user_object'
|
||||||
form_class = ImageForm
|
form_class = ImageForm
|
||||||
def get_context_data(self,*args,**kwargs):
|
|
||||||
context = super().get_context_data(*args,**kwargs)
|
def get_context_data(self, *args, **kwargs):
|
||||||
context['form'] = self.form_class(self.request.POST,self.request.FILES)
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context['form'] = self.form_class(self.request.POST, self.request.FILES)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id})
|
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id})
|
||||||
|
|
||||||
def post(self,request,*args,**kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
|
@ -230,7 +234,7 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
||||||
print(form)
|
print(form)
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
def form_valid(self,form):
|
def form_valid(self, form):
|
||||||
image_field = form.cleaned_data['image']
|
image_field = form.cleaned_data['image']
|
||||||
x = form.cleaned_data['x']
|
x = form.cleaned_data['x']
|
||||||
y = form.cleaned_data['y']
|
y = form.cleaned_data['y']
|
||||||
|
@ -238,23 +242,24 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
||||||
h = form.cleaned_data['height']
|
h = form.cleaned_data['height']
|
||||||
# image crop and resize
|
# image crop and resize
|
||||||
image_file = io.BytesIO(image_field.read())
|
image_file = io.BytesIO(image_field.read())
|
||||||
ext = image_field.name.split('.')[-1]
|
# ext = image_field.name.split('.')[-1].lower()
|
||||||
|
# TODO: support GIF format
|
||||||
image = Image.open(image_file)
|
image = Image.open(image_file)
|
||||||
image = image.crop((x, y, x+w, y+h))
|
image = image.crop((x, y, x + w, y + h))
|
||||||
image_clean = image.resize((settings.PIC_WIDTH,
|
image_clean = image.resize((settings.PIC_WIDTH,
|
||||||
settings.PIC_RATIO*settings.PIC_WIDTH),
|
settings.PIC_RATIO * settings.PIC_WIDTH),
|
||||||
Image.ANTIALIAS)
|
Image.ANTIALIAS)
|
||||||
image_file = io.BytesIO()
|
image_file = io.BytesIO()
|
||||||
image_clean.save(image_file,ext)
|
image_clean.save(image_file, "PNG")
|
||||||
image_field.file = image_file
|
image_field.file = image_file
|
||||||
# renaming
|
# renaming
|
||||||
filename = "{}_pic.{}".format(self.object.note.pk, ext)
|
filename = "{}_pic.png".format(self.object.note.pk)
|
||||||
image_field.name = filename
|
image_field.name = filename
|
||||||
self.object.note.display_image = image_field
|
self.object.note.display_image = image_field
|
||||||
self.object.note.save()
|
self.object.note.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
Affiche le jeton d'authentification, et permet de le regénérer
|
Affiche le jeton d'authentification, et permet de le regénérer
|
||||||
|
@ -282,6 +287,7 @@ class UserAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
"""
|
"""
|
||||||
Auto complete users by usernames
|
Auto complete users by usernames
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
||||||
|
@ -294,7 +300,7 @@ class UserAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
qs = User.objects.all()
|
qs = User.objects.all()
|
||||||
|
|
||||||
if self.q:
|
if self.q:
|
||||||
qs = qs.filter(username__regex=self.q)
|
qs = qs.filter(username__regex="^" + self.q)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@ -330,7 +336,7 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
club = context["club"]
|
club = context["club"]
|
||||||
club_transactions = \
|
club_transactions = \
|
||||||
Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))
|
Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))
|
||||||
context['history_list'] = HistoryTable(club_transactions)
|
context['history_list'] = HistoryTable(club_transactions)
|
||||||
club_member = \
|
club_member = \
|
||||||
|
|
|
@ -47,11 +47,11 @@ class NoteClubAdmin(PolymorphicChildModelAdmin):
|
||||||
"""
|
"""
|
||||||
Child for a club note, see NoteAdmin
|
Child for a club note, see NoteAdmin
|
||||||
"""
|
"""
|
||||||
inlines = (AliasInlines, )
|
inlines = (AliasInlines,)
|
||||||
|
|
||||||
# We can't change club after creation or the balance
|
# We can't change club after creation or the balance
|
||||||
readonly_fields = ('club', 'balance')
|
readonly_fields = ('club', 'balance')
|
||||||
search_fields = ('club', )
|
search_fields = ('club',)
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request):
|
||||||
"""
|
"""
|
||||||
|
@ -71,7 +71,7 @@ class NoteSpecialAdmin(PolymorphicChildModelAdmin):
|
||||||
"""
|
"""
|
||||||
Child for a special note, see NoteAdmin
|
Child for a special note, see NoteAdmin
|
||||||
"""
|
"""
|
||||||
readonly_fields = ('balance', )
|
readonly_fields = ('balance',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(NoteUser)
|
@admin.register(NoteUser)
|
||||||
|
@ -79,7 +79,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||||
"""
|
"""
|
||||||
Child for an user note, see NoteAdmin
|
Child for an user note, see NoteAdmin
|
||||||
"""
|
"""
|
||||||
inlines = (AliasInlines, )
|
inlines = (AliasInlines,)
|
||||||
|
|
||||||
# We can't change user after creation or the balance
|
# We can't change user after creation or the balance
|
||||||
readonly_fields = ('user', 'balance')
|
readonly_fields = ('user', 'balance')
|
||||||
|
@ -133,7 +133,7 @@ class TransactionAdmin(PolymorphicParentModelAdmin):
|
||||||
Else the amount of money would not be transferred
|
Else the amount of money would not be transferred
|
||||||
"""
|
"""
|
||||||
if obj: # user is editing an existing object
|
if obj: # user is editing an existing object
|
||||||
return 'created_at', 'source', 'destination', 'quantity',\
|
return 'created_at', 'source', 'destination', 'quantity', \
|
||||||
'amount'
|
'amount'
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -143,9 +143,9 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin customisation for TransactionTemplate
|
Admin customisation for TransactionTemplate
|
||||||
"""
|
"""
|
||||||
list_display = ('name', 'poly_destination', 'amount', 'category', 'display', )
|
list_display = ('name', 'poly_destination', 'amount', 'category', 'display',)
|
||||||
list_filter = ('category', 'display')
|
list_filter = ('category', 'display')
|
||||||
autocomplete_fields = ('destination', )
|
autocomplete_fields = ('destination',)
|
||||||
|
|
||||||
def poly_destination(self, obj):
|
def poly_destination(self, obj):
|
||||||
"""
|
"""
|
||||||
|
@ -161,5 +161,5 @@ class TemplateCategoryAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin customisation for TransactionTemplate
|
Admin customisation for TransactionTemplate
|
||||||
"""
|
"""
|
||||||
list_display = ('name', )
|
list_display = ('name',)
|
||||||
list_filter = ('name', )
|
list_filter = ('name',)
|
||||||
|
|
|
@ -5,7 +5,8 @@ from rest_framework import serializers
|
||||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||||
|
|
||||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
||||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
|
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
||||||
|
TemplateTransaction, SpecialTransaction
|
||||||
|
|
||||||
|
|
||||||
class NoteSerializer(serializers.ModelSerializer):
|
class NoteSerializer(serializers.ModelSerializer):
|
||||||
|
@ -13,15 +14,10 @@ class NoteSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Notes.
|
REST API Serializer for Notes.
|
||||||
The djangorestframework plugin will analyse the model `Note` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Note` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Note
|
model = Note
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
extra_kwargs = {
|
|
||||||
'url': {
|
|
||||||
'view_name': 'project-detail',
|
|
||||||
'lookup_field': 'pk'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NoteClubSerializer(serializers.ModelSerializer):
|
class NoteClubSerializer(serializers.ModelSerializer):
|
||||||
|
@ -29,40 +25,60 @@ class NoteClubSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Club's notes.
|
REST API Serializer for Club's notes.
|
||||||
The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NoteClub
|
model = NoteClub
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
class NoteSpecialSerializer(serializers.ModelSerializer):
|
class NoteSpecialSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for special notes.
|
REST API Serializer for special notes.
|
||||||
The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NoteSpecial
|
model = NoteSpecial
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
class NoteUserSerializer(serializers.ModelSerializer):
|
class NoteUserSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for User's notes.
|
REST API Serializer for User's notes.
|
||||||
The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NoteUser
|
model = NoteUser
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
class AliasSerializer(serializers.ModelSerializer):
|
class AliasSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for Aliases.
|
REST API Serializer for Aliases.
|
||||||
The djangorestframework plugin will analyse the model `Alias` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Alias` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
note = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_note(self, alias):
|
||||||
|
return NotePolymorphicSerializer().to_representation(alias.note)
|
||||||
|
|
||||||
|
|
||||||
class NotePolymorphicSerializer(PolymorphicSerializer):
|
class NotePolymorphicSerializer(PolymorphicSerializer):
|
||||||
model_serializer_mapping = {
|
model_serializer_mapping = {
|
||||||
|
@ -73,11 +89,23 @@ class NotePolymorphicSerializer(PolymorphicSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateCategorySerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Transaction templates.
|
||||||
|
The djangorestframework plugin will analyse the model `TemplateCategory` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TemplateCategory
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateSerializer(serializers.ModelSerializer):
|
class TransactionTemplateSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for Transaction templates.
|
REST API Serializer for Transaction templates.
|
||||||
The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -88,16 +116,49 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
REST API Serializer for Transactions.
|
REST API Serializer for Transactions.
|
||||||
The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateTransactionSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Transactions.
|
||||||
|
The djangorestframework plugin will analyse the model `TemplateTransaction` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TemplateTransaction
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransactionSerializer(serializers.ModelSerializer):
|
class MembershipTransactionSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for Membership transactions.
|
REST API Serializer for Membership transactions.
|
||||||
The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MembershipTransaction
|
model = MembershipTransaction
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialTransactionSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Special transactions.
|
||||||
|
The djangorestframework plugin will analyse the model `SpecialTransaction` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SpecialTransaction
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionPolymorphicSerializer(PolymorphicSerializer):
|
||||||
|
model_serializer_mapping = {
|
||||||
|
Transaction: TransactionSerializer,
|
||||||
|
TemplateTransaction: TemplateTransactionSerializer,
|
||||||
|
MembershipTransaction: MembershipTransactionSerializer,
|
||||||
|
SpecialTransaction: SpecialTransactionSerializer,
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import NotePolymorphicViewSet, AliasViewSet, \
|
from .views import NotePolymorphicViewSet, AliasViewSet, \
|
||||||
TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet
|
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet
|
||||||
|
|
||||||
|
|
||||||
def register_note_urls(router, path):
|
def register_note_urls(router, path):
|
||||||
|
@ -12,6 +12,6 @@ def register_note_urls(router, path):
|
||||||
router.register(path + '/note', NotePolymorphicViewSet)
|
router.register(path + '/note', NotePolymorphicViewSet)
|
||||||
router.register(path + '/alias', AliasViewSet)
|
router.register(path + '/alias', AliasViewSet)
|
||||||
|
|
||||||
|
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
||||||
router.register(path + '/transaction/transaction', TransactionViewSet)
|
router.register(path + '/transaction/transaction', TransactionViewSet)
|
||||||
router.register(path + '/transaction/template', TransactionTemplateViewSet)
|
router.register(path + '/transaction/template', TransactionTemplateViewSet)
|
||||||
router.register(path + '/transaction/membership', MembershipTransactionViewSet)
|
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
|
||||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
|
||||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
|
|
||||||
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
|
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
|
||||||
NoteUserSerializer, AliasSerializer, \
|
NoteUserSerializer, AliasSerializer, \
|
||||||
TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer
|
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||||
|
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
||||||
|
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
|
||||||
|
|
||||||
|
|
||||||
class NoteViewSet(viewsets.ModelViewSet):
|
class NoteViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -59,6 +61,9 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Note.objects.all()
|
queryset = Note.objects.all()
|
||||||
serializer_class = NotePolymorphicSerializer
|
serializer_class = NotePolymorphicSerializer
|
||||||
|
filter_backends = [SearchFilter, OrderingFilter]
|
||||||
|
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
|
||||||
|
ordering_fields = ['alias__name', 'alias__normalized_name']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
|
@ -69,8 +74,8 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", ".*")
|
alias = self.request.query_params.get("alias", ".*")
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(alias__name__regex=alias)
|
Q(alias__name__regex="^" + alias)
|
||||||
| Q(alias__normalized_name__regex=alias.lower()))
|
| Q(alias__normalized_name__regex="^" + alias.lower()))
|
||||||
|
|
||||||
note_type = self.request.query_params.get("type", None)
|
note_type = self.request.query_params.get("type", None)
|
||||||
if note_type:
|
if note_type:
|
||||||
|
@ -80,12 +85,11 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet):
|
||||||
elif "club" in types:
|
elif "club" in types:
|
||||||
queryset = queryset.filter(polymorphic_ctype__model="noteclub")
|
queryset = queryset.filter(polymorphic_ctype__model="noteclub")
|
||||||
elif "special" in types:
|
elif "special" in types:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(polymorphic_ctype__model="notespecial")
|
||||||
polymorphic_ctype__model="notespecial")
|
|
||||||
else:
|
else:
|
||||||
queryset = queryset.none()
|
queryset = queryset.none()
|
||||||
|
|
||||||
return queryset
|
return queryset.distinct()
|
||||||
|
|
||||||
|
|
||||||
class AliasViewSet(viewsets.ModelViewSet):
|
class AliasViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -96,6 +100,9 @@ class AliasViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects.all()
|
||||||
serializer_class = AliasSerializer
|
serializer_class = AliasSerializer
|
||||||
|
filter_backends = [SearchFilter, OrderingFilter]
|
||||||
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
|
ordering_fields = ['name', 'normalized_name']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
|
@ -107,7 +114,7 @@ class AliasViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", ".*")
|
alias = self.request.query_params.get("alias", ".*")
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(name__regex=alias) | Q(normalized_name__regex=alias.lower()))
|
Q(name__regex="^" + alias) | Q(normalized_name__regex="^" + alias.lower()))
|
||||||
|
|
||||||
note_id = self.request.query_params.get("note", None)
|
note_id = self.request.query_params.get("note", None)
|
||||||
if note_id:
|
if note_id:
|
||||||
|
@ -131,6 +138,18 @@ class AliasViewSet(viewsets.ModelViewSet):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateCategoryViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
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.all()
|
||||||
|
serializer_class = TemplateCategorySerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
|
@ -139,6 +158,8 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = TransactionTemplate.objects.all()
|
queryset = TransactionTemplate.objects.all()
|
||||||
serializer_class = TransactionTemplateSerializer
|
serializer_class = TransactionTemplateSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionViewSet(viewsets.ModelViewSet):
|
class TransactionViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -148,14 +169,6 @@ class TransactionViewSet(viewsets.ModelViewSet):
|
||||||
then render it on /api/note/transaction/transaction/
|
then render it on /api/note/transaction/transaction/
|
||||||
"""
|
"""
|
||||||
queryset = Transaction.objects.all()
|
queryset = Transaction.objects.all()
|
||||||
serializer_class = TransactionSerializer
|
serializer_class = TransactionPolymorphicSerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$reason', ]
|
||||||
class MembershipTransactionViewSet(viewsets.ModelViewSet):
|
|
||||||
"""
|
|
||||||
REST API View set.
|
|
||||||
The djangorestframework plugin will get all `MembershipTransaction` objects, serialize it to JSON with the given serializer,
|
|
||||||
then render it on /api/note/transaction/membership/
|
|
||||||
"""
|
|
||||||
queryset = MembershipTransaction.objects.all()
|
|
||||||
serializer_class = MembershipTransactionSerializer
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 22,
|
"polymorphic_ctype": 40,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 22,
|
"polymorphic_ctype": 40,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 3,
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 22,
|
"polymorphic_ctype": 40,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 4,
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 22,
|
"polymorphic_ctype": 40,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 5,
|
"pk": 5,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 21,
|
"polymorphic_ctype": 39,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
"model": "note.note",
|
"model": "note.note",
|
||||||
"pk": 6,
|
"pk": 6,
|
||||||
"fields": {
|
"fields": {
|
||||||
"polymorphic_ctype": 21,
|
"polymorphic_ctype": 39,
|
||||||
"balance": 0,
|
"balance": 0,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "",
|
||||||
|
|
|
@ -3,31 +3,25 @@
|
||||||
|
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import os
|
from .models import Alias
|
||||||
|
from .models import TransactionTemplate
|
||||||
|
|
||||||
from crispy_forms.helper import FormHelper
|
|
||||||
from crispy_forms.bootstrap import Div
|
|
||||||
from crispy_forms.layout import Layout, HTML
|
|
||||||
|
|
||||||
from .models import Transaction, TransactionTemplate, TemplateTransaction
|
|
||||||
from .models import Note, Alias
|
|
||||||
|
|
||||||
class AliasForm(forms.ModelForm):
|
class AliasForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = ("name",)
|
fields = ("name",)
|
||||||
|
|
||||||
def __init__(self,*args,**kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args,**kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["name"].label = False
|
self.fields["name"].label = False
|
||||||
self.fields["name"].widget.attrs={"placeholder":_('New Alias')}
|
self.fields["name"].widget.attrs = {"placeholder": _('New Alias')}
|
||||||
|
|
||||||
|
|
||||||
class ImageForm(forms.Form):
|
class ImageForm(forms.Form):
|
||||||
image = forms.ImageField(required = False,
|
image = forms.ImageField(required=False,
|
||||||
label=_('select an image'),
|
label=_('select an image'),
|
||||||
help_text=_('Maximal size: 2MB'))
|
help_text=_('Maximal size: 2MB'))
|
||||||
x = forms.FloatField(widget=forms.HiddenInput())
|
x = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
@ -35,7 +29,7 @@ class ImageForm(forms.Form):
|
||||||
width = forms.FloatField(widget=forms.HiddenInput())
|
width = forms.FloatField(widget=forms.HiddenInput())
|
||||||
height = forms.FloatField(widget=forms.HiddenInput())
|
height = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateForm(forms.ModelForm):
|
class TransactionTemplateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
|
@ -48,92 +42,11 @@ class TransactionTemplateForm(forms.ModelForm):
|
||||||
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
|
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
|
||||||
widgets = {
|
widgets = {
|
||||||
'destination':
|
'destination':
|
||||||
autocomplete.ModelSelect2(
|
autocomplete.ModelSelect2(
|
||||||
url='note:note_autocomplete',
|
url='note:note_autocomplete',
|
||||||
attrs={
|
attrs={
|
||||||
'data-placeholder': 'Note ...',
|
'data-placeholder': 'Note ...',
|
||||||
'data-minimum-input-length': 1,
|
'data-minimum-input-length': 1,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionForm(forms.ModelForm):
|
|
||||||
def save(self, commit=True):
|
|
||||||
super().save(commit)
|
|
||||||
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
"""
|
|
||||||
If the user has no right to transfer funds, then it will be the source of the transfer by default.
|
|
||||||
Transactions between a note and the same note are not authorized.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cleaned_data = super().clean()
|
|
||||||
if not "source" in cleaned_data: # TODO Replace it with "if %user has no right to transfer funds"
|
|
||||||
cleaned_data["source"] = self.user.note
|
|
||||||
|
|
||||||
if cleaned_data["source"].pk == cleaned_data["destination"].pk:
|
|
||||||
self.add_error("destination", _("Source and destination must be different."))
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Transaction
|
|
||||||
fields = (
|
|
||||||
'source',
|
|
||||||
'destination',
|
|
||||||
'reason',
|
|
||||||
'amount',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Voir ci-dessus
|
|
||||||
widgets = {
|
|
||||||
'source':
|
|
||||||
autocomplete.ModelSelect2(
|
|
||||||
url='note:note_autocomplete',
|
|
||||||
attrs={
|
|
||||||
'data-placeholder': 'Note ...',
|
|
||||||
'data-minimum-input-length': 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
'destination':
|
|
||||||
autocomplete.ModelSelect2(
|
|
||||||
url='note:note_autocomplete',
|
|
||||||
attrs={
|
|
||||||
'data-placeholder': 'Note ...',
|
|
||||||
'data-minimum-input-length': 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoForm(forms.ModelForm):
|
|
||||||
def save(self, commit=True):
|
|
||||||
button: TransactionTemplate = TransactionTemplate.objects.filter(
|
|
||||||
name=self.data['button']).get()
|
|
||||||
self.instance.destination = button.destination
|
|
||||||
self.instance.amount = button.amount
|
|
||||||
self.instance.reason = '{} ({})'.format(button.name, button.category)
|
|
||||||
self.instance.name = button.name
|
|
||||||
self.instance.category = button.category
|
|
||||||
super().save(commit)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = TemplateTransaction
|
|
||||||
fields = ('source', )
|
|
||||||
|
|
||||||
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
|
|
||||||
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
|
|
||||||
# et récupère les aliases de note valides
|
|
||||||
widgets = {
|
|
||||||
'source':
|
|
||||||
autocomplete.ModelSelect2(
|
|
||||||
url='note:note_autocomplete',
|
|
||||||
attrs={
|
|
||||||
'data-placeholder': 'Note ...',
|
|
||||||
'data-minimum-input-length': 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Defines each note types
|
Defines each note types
|
||||||
"""
|
"""
|
||||||
|
@ -27,7 +28,7 @@ class Note(PolymorphicModel):
|
||||||
help_text=_('in centimes, money credited for this instance'),
|
help_text=_('in centimes, money credited for this instance'),
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
last_negative= models.DateTimeField(
|
last_negative = models.DateTimeField(
|
||||||
verbose_name=_('last negative date'),
|
verbose_name=_('last negative date'),
|
||||||
help_text=_('last time the balance was negative'),
|
help_text=_('last time the balance was negative'),
|
||||||
null=True,
|
null=True,
|
||||||
|
@ -98,7 +99,7 @@ class Note(PolymorphicModel):
|
||||||
# Alias exists, so check if it is linked to this note
|
# Alias exists, so check if it is linked to this note
|
||||||
if aliases.first().note != self:
|
if aliases.first().note != self:
|
||||||
raise ValidationError(_('This alias is already taken.'),
|
raise ValidationError(_('This alias is already taken.'),
|
||||||
code="same_alias",)
|
code="same_alias", )
|
||||||
else:
|
else:
|
||||||
# Alias does not exist yet, so check if it can exist
|
# Alias does not exist yet, so check if it can exist
|
||||||
a = Alias(name=str(self))
|
a = Alias(name=str(self))
|
||||||
|
@ -208,6 +209,10 @@ class Alias(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("alias")
|
verbose_name = _("alias")
|
||||||
verbose_name_plural = _("aliases")
|
verbose_name_plural = _("aliases")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['name']),
|
||||||
|
models.Index(fields=['normalized_name']),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -230,13 +235,13 @@ class Alias(models.Model):
|
||||||
try:
|
try:
|
||||||
sim_alias = Alias.objects.get(normalized_name=normalized_name)
|
sim_alias = Alias.objects.get(normalized_name=normalized_name)
|
||||||
if self != sim_alias:
|
if self != sim_alias:
|
||||||
raise ValidationError(_('An alias with a similar name already exists: {} '.format(sim_alias)),
|
raise ValidationError(_('An alias with a similar name already exists: {} ').format(sim_alias),
|
||||||
code="same_alias"
|
code="same_alias"
|
||||||
)
|
)
|
||||||
except Alias.DoesNotExist:
|
except Alias.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
self.normalized_name = normalized_name
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
raise ValidationError(_("You can't delete your main alias."),
|
raise ValidationError(_("You can't delete your main alias."),
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse
|
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from .notes import Note, NoteClub
|
from .notes import Note, NoteClub, NoteSpecial
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Defines transactions
|
Defines transactions
|
||||||
|
@ -44,7 +44,7 @@ class TransactionTemplate(models.Model):
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
unique=True,
|
unique=True,
|
||||||
error_messages={'unique':_("A template with this name already exist")},
|
error_messages={'unique': _("A template with this name already exist")},
|
||||||
)
|
)
|
||||||
destination = models.ForeignKey(
|
destination = models.ForeignKey(
|
||||||
NoteClub,
|
NoteClub,
|
||||||
|
@ -63,11 +63,12 @@ class TransactionTemplate(models.Model):
|
||||||
max_length=31,
|
max_length=31,
|
||||||
)
|
)
|
||||||
display = models.BooleanField(
|
display = models.BooleanField(
|
||||||
default = True,
|
default=True,
|
||||||
)
|
)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -75,7 +76,7 @@ class TransactionTemplate(models.Model):
|
||||||
verbose_name_plural = _("transaction templates")
|
verbose_name_plural = _("transaction templates")
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('note:template_update', args=(self.pk, ))
|
return reverse('note:template_update', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class Transaction(PolymorphicModel):
|
class Transaction(PolymorphicModel):
|
||||||
|
@ -106,7 +107,10 @@ class Transaction(PolymorphicModel):
|
||||||
verbose_name=_('quantity'),
|
verbose_name=_('quantity'),
|
||||||
default=1,
|
default=1,
|
||||||
)
|
)
|
||||||
amount = models.PositiveIntegerField(verbose_name=_('amount'), )
|
amount = models.PositiveIntegerField(
|
||||||
|
verbose_name=_('amount'),
|
||||||
|
)
|
||||||
|
|
||||||
reason = models.CharField(
|
reason = models.CharField(
|
||||||
verbose_name=_('reason'),
|
verbose_name=_('reason'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
@ -119,6 +123,11 @@ class Transaction(PolymorphicModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("transaction")
|
verbose_name = _("transaction")
|
||||||
verbose_name_plural = _("transactions")
|
verbose_name_plural = _("transactions")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['created_at']),
|
||||||
|
models.Index(fields=['source']),
|
||||||
|
models.Index(fields=['destination']),
|
||||||
|
]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -127,6 +136,7 @@ class Transaction(PolymorphicModel):
|
||||||
|
|
||||||
if self.source.pk == self.destination.pk:
|
if self.source.pk == self.destination.pk:
|
||||||
# When source == destination, no money is transfered
|
# When source == destination, no money is transfered
|
||||||
|
super().save(*args, **kwargs)
|
||||||
return
|
return
|
||||||
|
|
||||||
created = self.pk is None
|
created = self.pk is None
|
||||||
|
@ -151,11 +161,14 @@ class Transaction(PolymorphicModel):
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.amount * self.quantity
|
return self.amount * self.quantity
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return _('Transfer')
|
||||||
|
|
||||||
|
|
||||||
class TemplateTransaction(Transaction):
|
class TemplateTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = models.ForeignKey(
|
template = models.ForeignKey(
|
||||||
|
@ -168,6 +181,37 @@ class TemplateTransaction(Transaction):
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return _('Template')
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialTransaction(Transaction):
|
||||||
|
"""
|
||||||
|
Special type of :model:`note.Transaction` associated to transactions with special notes
|
||||||
|
"""
|
||||||
|
|
||||||
|
last_name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
first_name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("first_name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
bank = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("bank"),
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransaction(Transaction):
|
class MembershipTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||||
|
@ -183,3 +227,7 @@ class MembershipTransaction(Transaction):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("membership transaction")
|
verbose_name = _("membership transaction")
|
||||||
verbose_name_plural = _("membership transactions")
|
verbose_name_plural = _("membership transactions")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return _('membership transaction')
|
||||||
|
|
|
@ -1,45 +1,77 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import html
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
from .models.transactions import Transaction
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models.notes import Alias
|
from .models.notes import Alias
|
||||||
|
from .models.transactions import Transaction
|
||||||
|
from .templatetags.pretty_money import pretty_money
|
||||||
|
|
||||||
|
|
||||||
class HistoryTable(tables.Table):
|
class HistoryTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'class':
|
'class':
|
||||||
'table table-condensed table-striped table-hover'
|
'table table-condensed table-striped table-hover'
|
||||||
}
|
}
|
||||||
model = Transaction
|
model = Transaction
|
||||||
|
exclude = ("id", "polymorphic_ctype", )
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
sequence = ('...', 'total', 'valid')
|
sequence = ('...', 'type', 'total', 'valid', )
|
||||||
|
orderable = False
|
||||||
|
|
||||||
|
type = tables.Column()
|
||||||
|
|
||||||
total = tables.Column() # will use Transaction.total() !!
|
total = tables.Column() # will use Transaction.total() !!
|
||||||
|
|
||||||
|
valid = tables.Column(attrs={"td": {"id": lambda record: "validate_" + str(record.id),
|
||||||
|
"class": lambda record: str(record.valid).lower() + ' validate',
|
||||||
|
"onclick": lambda record: 'de_validate(' + str(record.id) + ', '
|
||||||
|
+ str(record.valid).lower() + ')'}})
|
||||||
|
|
||||||
def order_total(self, queryset, is_descending):
|
def order_total(self, queryset, is_descending):
|
||||||
# needed for rendering
|
# needed for rendering
|
||||||
queryset = queryset.annotate(total=F('amount') * F('quantity')) \
|
queryset = queryset.annotate(total=F('amount') * F('quantity')) \
|
||||||
.order_by(('-' if is_descending else '') + 'total')
|
.order_by(('-' if is_descending else '') + 'total')
|
||||||
return (queryset, True)
|
return queryset, True
|
||||||
|
|
||||||
|
def render_amount(self, value):
|
||||||
|
return pretty_money(value)
|
||||||
|
|
||||||
|
def render_total(self, value):
|
||||||
|
return pretty_money(value)
|
||||||
|
|
||||||
|
def render_type(self, value):
|
||||||
|
return _(value)
|
||||||
|
|
||||||
|
# Django-tables escape strings. That's a wrong thing.
|
||||||
|
def render_reason(self, value):
|
||||||
|
return html.unescape(value)
|
||||||
|
|
||||||
|
def render_valid(self, value):
|
||||||
|
return "✔" if value else "✖"
|
||||||
|
|
||||||
|
|
||||||
class AliasTable(tables.Table):
|
class AliasTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'class':
|
'class':
|
||||||
'table table condensed table-striped table-hover'
|
'table table condensed table-striped table-hover'
|
||||||
}
|
}
|
||||||
model = Alias
|
model = Alias
|
||||||
fields =('name',)
|
fields = ('name',)
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
|
||||||
show_header = False
|
show_header = False
|
||||||
name = tables.Column(attrs={'td':{'class':'text-center'}})
|
name = tables.Column(attrs={'td': {'class': 'text-center'}})
|
||||||
delete = tables.LinkColumn('member:user_alias_delete',
|
delete = tables.LinkColumn('member:user_alias_delete',
|
||||||
args=[A('pk')],
|
args=[A('pk')],
|
||||||
attrs={
|
attrs={
|
||||||
'td': {'class':'col-sm-2'},
|
'td': {'class': 'col-sm-2'},
|
||||||
'a': {'class': 'btn btn-danger'} },
|
'a': {'class': 'btn btn-danger'}},
|
||||||
text='delete',accessor='pk')
|
text='delete', accessor='pk')
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def getenv(value):
|
||||||
|
return os.getenv(value)
|
||||||
|
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
register.filter('getenv', getenv)
|
|
@ -11,7 +11,7 @@ def pretty_money(value):
|
||||||
abs(value) // 100,
|
abs(value) // 100,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return "{:s}{:d} € {:02d}".format(
|
return "{:s}{:d}.{:02d} €".format(
|
||||||
"- " if value < 0 else "",
|
"- " if value < 0 else "",
|
||||||
abs(value) // 100,
|
abs(value) // 100,
|
||||||
abs(value) % 100,
|
abs(value) % 100,
|
||||||
|
|
|
@ -3,56 +3,49 @@
|
||||||
|
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, ListView, UpdateView
|
from django.views.generic import CreateView, ListView, UpdateView
|
||||||
|
from django_tables2 import SingleTableView
|
||||||
|
|
||||||
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction
|
from .forms import TransactionTemplateForm
|
||||||
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
|
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction, NoteSpecial
|
||||||
|
from .models.transactions import SpecialTransaction
|
||||||
|
from .tables import HistoryTable
|
||||||
|
|
||||||
|
|
||||||
class TransactionCreate(LoginRequiredMixin, CreateView):
|
class TransactionCreate(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
Show transfer page
|
Show transfer page
|
||||||
|
|
||||||
TODO: If user have sufficient rights, they can transfer from an other note
|
TODO: If user have sufficient rights, they can transfer from an other note
|
||||||
"""
|
"""
|
||||||
model = Transaction
|
queryset = Transaction.objects.order_by("-id").all()[:50]
|
||||||
form_class = TransactionForm
|
template_name = "note/transaction_form.html"
|
||||||
|
|
||||||
|
# Transaction history table
|
||||||
|
table_class = HistoryTable
|
||||||
|
table_pagination = {"per_page": 50}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Add some context variables in template such as page title
|
Add some context variables in template such as page title
|
||||||
"""
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['title'] = _('Transfer money from your account '
|
context['title'] = _('Transfer money')
|
||||||
'to one or others')
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
||||||
|
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
|
||||||
context['no_cache'] = True
|
context['special_types'] = NoteSpecial.objects.order_by("special_type").all()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
|
||||||
"""
|
|
||||||
If the user has no right to transfer funds, then it won't have the choice of the source of the transfer.
|
|
||||||
"""
|
|
||||||
form = super().get_form(form_class)
|
|
||||||
|
|
||||||
if False: # TODO: fix it with "if %user has no right to transfer funds"
|
|
||||||
del form.fields['source']
|
|
||||||
form.user = self.request.user
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse('note:transfer')
|
|
||||||
|
|
||||||
|
|
||||||
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
"""
|
"""
|
||||||
Auto complete note by aliases
|
Auto complete note by aliases
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
||||||
|
@ -66,7 +59,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
|
|
||||||
# self.q est le paramètre de la recherche
|
# self.q est le paramètre de la recherche
|
||||||
if self.q:
|
if self.q:
|
||||||
qs = qs.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q)))\
|
qs = qs.filter(Q(name__regex="^" + self.q) | Q(normalized_name__regex="^" + Alias.normalize(self.q))) \
|
||||||
.order_by('normalized_name').distinct()
|
.order_by('normalized_name').distinct()
|
||||||
|
|
||||||
# Filtrage par type de note (user, club, special)
|
# Filtrage par type de note (user, club, special)
|
||||||
|
@ -120,31 +113,31 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
form_class = TransactionTemplateForm
|
form_class = TransactionTemplateForm
|
||||||
|
|
||||||
|
|
||||||
class ConsoView(LoginRequiredMixin, CreateView):
|
class ConsoView(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
Consume
|
Consume
|
||||||
"""
|
"""
|
||||||
model = TemplateTransaction
|
queryset = Transaction.objects.order_by("-id").all()[:50]
|
||||||
template_name = "note/conso_form.html"
|
template_name = "note/conso_form.html"
|
||||||
form_class = ConsoForm
|
|
||||||
|
# Transaction history table
|
||||||
|
table_class = HistoryTable
|
||||||
|
table_pagination = {"per_page": 50}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Add some context variables in template such as page title
|
Add some context variables in template such as page title
|
||||||
"""
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['transaction_templates'] = TransactionTemplate.objects.filter(display=True) \
|
from django.db.models import Count
|
||||||
.order_by('category')
|
buttons = TransactionTemplate.objects.filter(display=True) \
|
||||||
context['title'] = _("Consommations")
|
.annotate(clicks=Count('templatetransaction')).order_by('category__name', 'name')
|
||||||
|
context['transaction_templates'] = buttons
|
||||||
|
context['most_used'] = buttons.order_by('-clicks', 'name')[:10]
|
||||||
|
context['title'] = _("Consumptions")
|
||||||
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(TemplateTransaction).pk
|
||||||
|
|
||||||
# select2 compatibility
|
# select2 compatibility
|
||||||
context['no_cache'] = True
|
context['no_cache'] = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
"""
|
|
||||||
When clicking a button, reload the same page
|
|
||||||
"""
|
|
||||||
return reverse('note:consos')
|
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,17 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
if [ -z ${NOTE_URL+x} ]; then
|
||||||
|
echo "Warning: your env files are not configurated."
|
||||||
|
else
|
||||||
|
sed -i -e "s/example.com/$DOMAIN/g" /code/apps/member/fixtures/initial.json
|
||||||
|
sed -i -e "s/localhost/$NOTE_URL/g" /code/note_kfet/fixtures/initial.json
|
||||||
|
sed -i -e "s/\.\*/https?:\/\/$NOTE_URL\/.*/g" /code/note_kfet/fixtures/cas.json
|
||||||
|
sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /code/note_kfet/fixtures/cas.json
|
||||||
|
fi
|
||||||
|
|
||||||
python manage.py compilemessages
|
python manage.py compilemessages
|
||||||
python manage.py makemigrations
|
python manage.py makemigrations
|
||||||
|
|
||||||
# Wait for database
|
|
||||||
sleep 5
|
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
# TODO: use uwsgi in production
|
|
||||||
python manage.py runserver 0.0.0.0:8000
|
python manage.py runserver 0.0.0.0:8000
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-02-27 17:39+0100\n"
|
"POT-Creation-Date: 2020-03-16 11:53+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -23,9 +23,10 @@ msgid "activity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||||
#: apps/member/models.py:60 apps/member/models.py:111
|
#: apps/member/models.py:61 apps/member/models.py:112
|
||||||
#: apps/note/models/notes.py:184 apps/note/models/transactions.py:24
|
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
|
||||||
#: apps/note/models/transactions.py:44 templates/member/profile_detail.html:11
|
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:202
|
||||||
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -49,8 +50,8 @@ msgstr ""
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/models.py:54 apps/note/models/notes.py:160
|
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||||
#: apps/note/models/transactions.py:62
|
#: apps/note/models/transactions.py:62 apps/note/models/transactions.py:115
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -86,43 +87,59 @@ msgstr ""
|
||||||
msgid "API"
|
msgid "API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/apps.py:10
|
#: apps/logs/apps.py:11
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:20 apps/note/models/notes.py:105
|
#: apps/logs/models.py:21 apps/note/models/notes.py:117
|
||||||
msgid "user"
|
msgid "user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:27
|
#: apps/logs/models.py:27
|
||||||
|
msgid "IP Address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/logs/models.py:35
|
||||||
msgid "model"
|
msgid "model"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:34
|
#: apps/logs/models.py:42
|
||||||
msgid "identifier"
|
msgid "identifier"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:39
|
#: apps/logs/models.py:47
|
||||||
msgid "previous data"
|
msgid "previous data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:44
|
#: apps/logs/models.py:52
|
||||||
msgid "new data"
|
msgid "new data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:51
|
#: apps/logs/models.py:60
|
||||||
|
msgid "create"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/logs/models.py:61
|
||||||
|
msgid "edit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/logs/models.py:62
|
||||||
|
msgid "delete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/logs/models.py:65
|
||||||
msgid "action"
|
msgid "action"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:59
|
#: apps/logs/models.py:73
|
||||||
msgid "timestamp"
|
msgid "timestamp"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:63
|
#: apps/logs/models.py:77
|
||||||
msgid "Logs cannot be destroyed."
|
msgid "Logs cannot be destroyed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/apps.py:10
|
#: apps/member/apps.py:14
|
||||||
msgid "member"
|
msgid "member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -130,7 +147,7 @@ msgstr ""
|
||||||
msgid "phone number"
|
msgid "phone number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:29 templates/member/profile_detail.html:24
|
#: apps/member/models.py:29 templates/member/profile_detail.html:28
|
||||||
msgid "section"
|
msgid "section"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -138,7 +155,7 @@ msgstr ""
|
||||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:36 templates/member/profile_detail.html:27
|
#: apps/member/models.py:36 templates/member/profile_detail.html:31
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -150,199 +167,207 @@ msgstr ""
|
||||||
msgid "user profile"
|
msgid "user profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:65
|
#: apps/member/models.py:66
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:70
|
#: apps/member/models.py:71
|
||||||
msgid "membership fee"
|
msgid "membership fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:74
|
#: apps/member/models.py:75
|
||||||
msgid "membership duration"
|
msgid "membership duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:75
|
#: apps/member/models.py:76
|
||||||
msgid "The longest time a membership can last (NULL = infinite)."
|
msgid "The longest time a membership can last (NULL = infinite)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:80
|
#: apps/member/models.py:81
|
||||||
msgid "membership start"
|
msgid "membership start"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:81
|
#: apps/member/models.py:82
|
||||||
msgid "How long after January 1st the members can renew their membership."
|
msgid "How long after January 1st the members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:86
|
#: apps/member/models.py:87
|
||||||
msgid "membership end"
|
msgid "membership end"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:87
|
#: apps/member/models.py:88
|
||||||
msgid ""
|
msgid ""
|
||||||
"How long the membership can last after January 1st of the next year after "
|
"How long the membership can last after January 1st of the next year after "
|
||||||
"members can renew their membership."
|
"members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:93 apps/note/models/notes.py:135
|
#: apps/member/models.py:94 apps/note/models/notes.py:139
|
||||||
msgid "club"
|
msgid "club"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:94
|
#: apps/member/models.py:95
|
||||||
msgid "clubs"
|
msgid "clubs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:117
|
#: apps/member/models.py:118
|
||||||
msgid "role"
|
msgid "role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:118
|
#: apps/member/models.py:119
|
||||||
msgid "roles"
|
msgid "roles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:142
|
#: apps/member/models.py:143
|
||||||
msgid "membership starts on"
|
msgid "membership starts on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:145
|
#: apps/member/models.py:146
|
||||||
msgid "membership ends on"
|
msgid "membership ends on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:149
|
#: apps/member/models.py:150
|
||||||
msgid "fee"
|
msgid "fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:153
|
#: apps/member/models.py:154
|
||||||
msgid "membership"
|
msgid "membership"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:154
|
#: apps/member/models.py:155
|
||||||
msgid "memberships"
|
msgid "memberships"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:63 templates/member/profile_detail.html:42
|
#: apps/member/views.py:69 templates/member/profile_detail.html:46
|
||||||
msgid "Update Profile"
|
msgid "Update Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:79
|
#: apps/member/views.py:82
|
||||||
msgid "An alias with a similar name already exists."
|
msgid "An alias with a similar name already exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:130
|
#: apps/member/views.py:132
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Account #%(id)s: %(username)s"
|
msgid "Account #%(id)s: %(username)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:93
|
#: apps/member/views.py:202
|
||||||
|
msgid "Alias successfully deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
||||||
msgid "source"
|
msgid "source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/admin.py:128 apps/note/admin.py:156
|
#: apps/note/admin.py:128 apps/note/admin.py:156
|
||||||
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:99
|
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:100
|
||||||
msgid "destination"
|
msgid "destination"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/apps.py:14 apps/note/models/notes.py:54
|
#: apps/note/apps.py:14 apps/note/models/notes.py:58
|
||||||
msgid "note"
|
msgid "note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/forms.py:49
|
#: apps/note/forms.py:20
|
||||||
msgid "Source and destination must be different."
|
msgid "New Alias"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:26
|
#: apps/note/forms.py:25
|
||||||
msgid "account balance"
|
msgid "select an image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/forms.py:26
|
||||||
|
msgid "Maximal size: 2MB"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:27
|
#: apps/note/models/notes.py:27
|
||||||
|
msgid "account balance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:28
|
||||||
msgid "in centimes, money credited for this instance"
|
msgid "in centimes, money credited for this instance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:31
|
#: apps/note/models/notes.py:32
|
||||||
msgid "last negative date"
|
msgid "last negative date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:32
|
#: apps/note/models/notes.py:33
|
||||||
msgid "last time the balance was negative"
|
msgid "last time the balance was negative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:37
|
#: apps/note/models/notes.py:38
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:40
|
#: apps/note/models/notes.py:41
|
||||||
msgid ""
|
msgid ""
|
||||||
"Designates whether this note should be treated as active. Unselect this "
|
"Designates whether this note should be treated as active. Unselect this "
|
||||||
"instead of deleting notes."
|
"instead of deleting notes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:44
|
#: apps/note/models/notes.py:45
|
||||||
msgid "display image"
|
msgid "display image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:49 apps/note/models/transactions.py:102
|
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:103
|
||||||
msgid "created at"
|
msgid "created at"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:55
|
#: apps/note/models/notes.py:59
|
||||||
msgid "notes"
|
msgid "notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:63
|
#: apps/note/models/notes.py:67
|
||||||
msgid "Note"
|
msgid "Note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:73 apps/note/models/notes.py:97
|
#: apps/note/models/notes.py:77 apps/note/models/notes.py:101
|
||||||
msgid "This alias is already taken."
|
msgid "This alias is already taken."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:113
|
#: apps/note/models/notes.py:121
|
||||||
msgid "user"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/notes.py:117
|
|
||||||
msgid "one's note"
|
msgid "one's note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:118
|
#: apps/note/models/notes.py:122
|
||||||
msgid "users note"
|
msgid "users note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:124
|
#: apps/note/models/notes.py:128
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(user)s's note"
|
msgid "%(user)s's note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:139
|
#: apps/note/models/notes.py:143
|
||||||
msgid "club note"
|
msgid "club note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:140
|
#: apps/note/models/notes.py:144
|
||||||
msgid "clubs notes"
|
msgid "clubs notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:146
|
#: apps/note/models/notes.py:150
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Note of %(club)s club"
|
msgid "Note of %(club)s club"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:166
|
#: apps/note/models/notes.py:170
|
||||||
msgid "special note"
|
msgid "special note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:167
|
#: apps/note/models/notes.py:171
|
||||||
msgid "special notes"
|
msgid "special notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:190
|
#: apps/note/models/notes.py:194
|
||||||
msgid "Invalid alias"
|
msgid "Invalid alias"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:206
|
#: apps/note/models/notes.py:210
|
||||||
msgid "alias"
|
msgid "alias"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:207 templates/member/profile_detail.html:33
|
#: apps/note/models/notes.py:211 templates/member/profile_detail.html:37
|
||||||
msgid "aliases"
|
msgid "aliases"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -351,10 +376,10 @@ msgid "Alias is too long."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:238
|
#: apps/note/models/notes.py:238
|
||||||
msgid "An alias with a similar name already exists:"
|
msgid "An alias with a similar name already exists: {} "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:246
|
#: apps/note/models/notes.py:247
|
||||||
msgid "You can't delete your main alias."
|
msgid "You can't delete your main alias."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -370,7 +395,7 @@ msgstr ""
|
||||||
msgid "A template with this name already exist"
|
msgid "A template with this name already exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:109
|
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:111
|
||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -378,59 +403,96 @@ msgstr ""
|
||||||
msgid "in centimes"
|
msgid "in centimes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:74
|
#: apps/note/models/transactions.py:75
|
||||||
msgid "transaction template"
|
msgid "transaction template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:75
|
#: apps/note/models/transactions.py:76
|
||||||
msgid "transaction templates"
|
msgid "transaction templates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:106
|
#: apps/note/models/transactions.py:107
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:111
|
#: apps/note/models/transactions.py:117 templates/note/transaction_form.html:15
|
||||||
msgid "reason"
|
msgid "Gift"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:115
|
#: apps/note/models/transactions.py:118 templates/base.html:90
|
||||||
msgid "valid"
|
#: templates/note/transaction_form.html:19
|
||||||
|
#: templates/note/transaction_form.html:126
|
||||||
|
msgid "Transfer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:120
|
#: apps/note/models/transactions.py:119
|
||||||
msgid "transaction"
|
msgid "Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:121
|
#: apps/note/models/transactions.py:120 templates/note/transaction_form.html:23
|
||||||
msgid "transactions"
|
msgid "Credit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:184
|
#: apps/note/models/transactions.py:121 templates/note/transaction_form.html:27
|
||||||
|
msgid "Debit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:122 apps/note/models/transactions.py:230
|
||||||
msgid "membership transaction"
|
msgid "membership transaction"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:185
|
#: apps/note/models/transactions.py:129
|
||||||
|
msgid "reason"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:133
|
||||||
|
msgid "valid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:138
|
||||||
|
msgid "transaction"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:139
|
||||||
|
msgid "transactions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:207
|
||||||
|
msgid "first_name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:212
|
||||||
|
msgid "bank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:231
|
||||||
msgid "membership transactions"
|
msgid "membership transactions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/views.py:29
|
#: apps/note/views.py:31
|
||||||
msgid "Transfer money from your account to one or others"
|
msgid "Transfer money"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/views.py:138
|
#: apps/note/views.py:132 templates/base.html:78
|
||||||
msgid "Consommations"
|
msgid "Consumptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:155
|
#: note_kfet/settings/__init__.py:61
|
||||||
msgid "German"
|
msgid ""
|
||||||
|
"The Central Authentication Service grants you access to most of our websites "
|
||||||
|
"by authenticating only once, so you don't need to type your credentials "
|
||||||
|
"again unless your session expires or you logout."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:156
|
#: note_kfet/settings/base.py:156
|
||||||
msgid "English"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:157
|
#: note_kfet/settings/base.py:157
|
||||||
|
msgid "English"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: note_kfet/settings/base.py:158
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -438,6 +500,78 @@ msgstr ""
|
||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/base.html:81
|
||||||
|
msgid "Clubs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/base.html:84
|
||||||
|
msgid "Activities"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/base.html:87
|
||||||
|
msgid "Buttons"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/base.html:7
|
||||||
|
msgid "Central Authentication Service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/base.html:43
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"A new version of the application is available. This instance runs "
|
||||||
|
"%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
|
||||||
|
"upgrading."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:4
|
||||||
|
msgid ""
|
||||||
|
"<h3>Log In Successful</h3>You have successfully logged into the Central "
|
||||||
|
"Authentication Service.<br/>For security reasons, please Log Out and Exit "
|
||||||
|
"your web browser when you are done accessing services that require "
|
||||||
|
"authentication!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:8
|
||||||
|
msgid "Log me out from all my sessions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:14
|
||||||
|
msgid "Forget the identity provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:18
|
||||||
|
msgid "Logout"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:6
|
||||||
|
msgid "Please log in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:11
|
||||||
|
msgid ""
|
||||||
|
"If you don't have any Note Kfet account, please follow <a href='/accounts/"
|
||||||
|
"signup'>this link to sign up</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:17
|
||||||
|
msgid "Login"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/warn.html:9
|
||||||
|
msgid "Connect to the service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/django_filters/rest_framework/crispy_form.html:4
|
||||||
|
#: templates/django_filters/rest_framework/form.html:2
|
||||||
|
msgid "Field filters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/django_filters/rest_framework/form.html:5
|
||||||
|
#: templates/member/club_form.html:10
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/club_detail.html:10
|
#: templates/member/club_detail.html:10
|
||||||
msgid "Membership starts on"
|
msgid "Membership starts on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -450,10 +584,22 @@ msgstr ""
|
||||||
msgid "Membership duration"
|
msgid "Membership duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:30
|
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34
|
||||||
msgid "balance"
|
msgid "balance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75
|
||||||
|
msgid "Transaction history"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/club_form.html:6
|
||||||
|
msgid "Clubs list"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/club_list.html:8
|
||||||
|
msgid "New club"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/manage_auth_tokens.html:16
|
#: templates/member/manage_auth_tokens.html:16
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -466,27 +612,35 @@ msgstr ""
|
||||||
msgid "Regenerate token"
|
msgid "Regenerate token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:11
|
#: templates/member/profile_alias.html:10
|
||||||
|
msgid "Add alias"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "first name"
|
msgid "first name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:14
|
#: templates/member/profile_detail.html:18
|
||||||
msgid "username"
|
msgid "username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:17
|
#: templates/member/profile_detail.html:21
|
||||||
msgid "password"
|
msgid "password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:20
|
#: templates/member/profile_detail.html:24
|
||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:38
|
#: templates/member/profile_detail.html:42
|
||||||
msgid "Manage auth token"
|
msgid "Manage auth token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:54
|
#: templates/member/profile_detail.html:49
|
||||||
|
msgid "View Profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/profile_detail.html:62
|
||||||
msgid "View my memberships"
|
msgid "View my memberships"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -494,12 +648,87 @@ msgstr ""
|
||||||
msgid "Save Changes"
|
msgid "Save Changes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/member/signup.html:5 templates/member/signup.html:8
|
||||||
#: templates/member/signup.html:14
|
#: templates/member/signup.html:14
|
||||||
msgid "Sign Up"
|
msgid "Sign up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:35
|
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:38
|
||||||
msgid "Transfer"
|
msgid "Select emitters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:45
|
||||||
|
msgid "Select consumptions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:51
|
||||||
|
msgid "Consume!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:64
|
||||||
|
msgid "Most used buttons"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:121
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:126
|
||||||
|
msgid "Single consumptions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:130
|
||||||
|
msgid "Double consumptions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:141
|
||||||
|
msgid "Recent transactions history"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:55
|
||||||
|
msgid "External payment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:63
|
||||||
|
msgid "Transfer type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:73
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:79
|
||||||
|
msgid "First name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:85
|
||||||
|
msgid "Bank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:97
|
||||||
|
#: templates/note/transaction_form.html:179
|
||||||
|
#: templates/note/transaction_form.html:186
|
||||||
|
msgid "Select receivers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:114
|
||||||
|
msgid "Amount"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:119
|
||||||
|
msgid "Reason"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:193
|
||||||
|
msgid "Credit note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:200
|
||||||
|
msgid "Debit note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_form.html:6
|
||||||
|
msgid "Buttons list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/logged_out.html:8
|
#: templates/registration/logged_out.html:8
|
||||||
|
@ -511,7 +740,7 @@ msgid "Log in again"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:7 templates/registration/login.html:8
|
#: templates/registration/login.html:7 templates/registration/login.html:8
|
||||||
#: templates/registration/login.html:22
|
#: templates/registration/login.html:26
|
||||||
#: templates/registration/password_reset_complete.html:10
|
#: templates/registration/password_reset_complete.html:10
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -523,7 +752,7 @@ msgid ""
|
||||||
"page. Would you like to login to a different account?"
|
"page. Would you like to login to a different account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:23
|
#: templates/registration/login.html:27
|
||||||
msgid "Forgotten your password or username?"
|
msgid "Forgotten your password or username?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-02-27 17:39+0100\n"
|
"POT-Creation-Date: 2020-03-16 11:53+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -18,9 +18,10 @@ msgid "activity"
|
||||||
msgstr "activité"
|
msgstr "activité"
|
||||||
|
|
||||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||||
#: apps/member/models.py:60 apps/member/models.py:111
|
#: apps/member/models.py:61 apps/member/models.py:112
|
||||||
#: apps/note/models/notes.py:184 apps/note/models/transactions.py:24
|
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
|
||||||
#: apps/note/models/transactions.py:44 templates/member/profile_detail.html:11
|
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:202
|
||||||
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "nom"
|
msgstr "nom"
|
||||||
|
|
||||||
|
@ -44,8 +45,8 @@ msgstr "types d'activité"
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
#: apps/activity/models.py:54 apps/note/models/notes.py:160
|
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||||
#: apps/note/models/transactions.py:62
|
#: apps/note/models/transactions.py:62 apps/note/models/transactions.py:115
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr "type"
|
msgstr "type"
|
||||||
|
|
||||||
|
@ -81,47 +82,59 @@ msgstr "invités"
|
||||||
msgid "API"
|
msgid "API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/apps.py:10
|
#: apps/logs/apps.py:11
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:20 apps/note/models/notes.py:105
|
#: apps/logs/models.py:21 apps/note/models/notes.py:117
|
||||||
msgid "user"
|
msgid "user"
|
||||||
msgstr "utilisateur"
|
msgstr "utilisateur"
|
||||||
|
|
||||||
#: apps/logs/models.py:27
|
#: apps/logs/models.py:27
|
||||||
|
msgid "IP Address"
|
||||||
|
msgstr "Adresse IP"
|
||||||
|
|
||||||
|
#: apps/logs/models.py:35
|
||||||
msgid "model"
|
msgid "model"
|
||||||
msgstr "Modèle"
|
msgstr "Modèle"
|
||||||
|
|
||||||
#: apps/logs/models.py:34
|
#: apps/logs/models.py:42
|
||||||
msgid "identifier"
|
msgid "identifier"
|
||||||
msgstr "Identifiant"
|
msgstr "Identifiant"
|
||||||
|
|
||||||
#: apps/logs/models.py:39
|
#: apps/logs/models.py:47
|
||||||
msgid "previous data"
|
msgid "previous data"
|
||||||
msgstr "Données précédentes"
|
msgstr "Données précédentes"
|
||||||
|
|
||||||
#: apps/logs/models.py:44
|
#: apps/logs/models.py:52
|
||||||
#, fuzzy
|
|
||||||
#| msgid "end date"
|
|
||||||
msgid "new data"
|
msgid "new data"
|
||||||
msgstr "Nouvelles données"
|
msgstr "Nouvelles données"
|
||||||
|
|
||||||
#: apps/logs/models.py:51
|
#: apps/logs/models.py:60
|
||||||
#, fuzzy
|
msgid "create"
|
||||||
#| msgid "section"
|
msgstr "Créer"
|
||||||
|
|
||||||
|
#: apps/logs/models.py:61
|
||||||
|
msgid "edit"
|
||||||
|
msgstr "Modifier"
|
||||||
|
|
||||||
|
#: apps/logs/models.py:62
|
||||||
|
msgid "delete"
|
||||||
|
msgstr "Supprimer"
|
||||||
|
|
||||||
|
#: apps/logs/models.py:65
|
||||||
msgid "action"
|
msgid "action"
|
||||||
msgstr "Action"
|
msgstr "Action"
|
||||||
|
|
||||||
#: apps/logs/models.py:59
|
#: apps/logs/models.py:73
|
||||||
msgid "timestamp"
|
msgid "timestamp"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
|
|
||||||
#: apps/logs/models.py:63
|
#: apps/logs/models.py:77
|
||||||
msgid "Logs cannot be destroyed."
|
msgid "Logs cannot be destroyed."
|
||||||
msgstr "Les logs ne peuvent pas être détruits."
|
msgstr "Les logs ne peuvent pas être détruits."
|
||||||
|
|
||||||
#: apps/member/apps.py:10
|
#: apps/member/apps.py:14
|
||||||
msgid "member"
|
msgid "member"
|
||||||
msgstr "adhérent"
|
msgstr "adhérent"
|
||||||
|
|
||||||
|
@ -129,7 +142,7 @@ msgstr "adhérent"
|
||||||
msgid "phone number"
|
msgid "phone number"
|
||||||
msgstr "numéro de téléphone"
|
msgstr "numéro de téléphone"
|
||||||
|
|
||||||
#: apps/member/models.py:29 templates/member/profile_detail.html:24
|
#: apps/member/models.py:29 templates/member/profile_detail.html:28
|
||||||
msgid "section"
|
msgid "section"
|
||||||
msgstr "section"
|
msgstr "section"
|
||||||
|
|
||||||
|
@ -137,7 +150,7 @@ msgstr "section"
|
||||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
|
|
||||||
#: apps/member/models.py:36 templates/member/profile_detail.html:27
|
#: apps/member/models.py:36 templates/member/profile_detail.html:31
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr "adresse"
|
msgstr "adresse"
|
||||||
|
|
||||||
|
@ -149,37 +162,37 @@ msgstr "payé"
|
||||||
msgid "user profile"
|
msgid "user profile"
|
||||||
msgstr "profil utilisateur"
|
msgstr "profil utilisateur"
|
||||||
|
|
||||||
#: apps/member/models.py:65
|
#: apps/member/models.py:66
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr "courriel"
|
msgstr "courriel"
|
||||||
|
|
||||||
#: apps/member/models.py:70
|
#: apps/member/models.py:71
|
||||||
msgid "membership fee"
|
msgid "membership fee"
|
||||||
msgstr "cotisation pour adhérer"
|
msgstr "cotisation pour adhérer"
|
||||||
|
|
||||||
#: apps/member/models.py:74
|
#: apps/member/models.py:75
|
||||||
msgid "membership duration"
|
msgid "membership duration"
|
||||||
msgstr "durée de l'adhésion"
|
msgstr "durée de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:75
|
#: apps/member/models.py:76
|
||||||
msgid "The longest time a membership can last (NULL = infinite)."
|
msgid "The longest time a membership can last (NULL = infinite)."
|
||||||
msgstr "La durée maximale d'une adhésion (NULL = infinie)."
|
msgstr "La durée maximale d'une adhésion (NULL = infinie)."
|
||||||
|
|
||||||
#: apps/member/models.py:80
|
#: apps/member/models.py:81
|
||||||
msgid "membership start"
|
msgid "membership start"
|
||||||
msgstr "début de l'adhésion"
|
msgstr "début de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:81
|
#: apps/member/models.py:82
|
||||||
msgid "How long after January 1st the members can renew their membership."
|
msgid "How long after January 1st the members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
|
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
|
||||||
"adhésion."
|
"adhésion."
|
||||||
|
|
||||||
#: apps/member/models.py:86
|
#: apps/member/models.py:87
|
||||||
msgid "membership end"
|
msgid "membership end"
|
||||||
msgstr "fin de l'adhésion"
|
msgstr "fin de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:87
|
#: apps/member/models.py:88
|
||||||
msgid ""
|
msgid ""
|
||||||
"How long the membership can last after January 1st of the next year after "
|
"How long the membership can last after January 1st of the next year after "
|
||||||
"members can renew their membership."
|
"members can renew their membership."
|
||||||
|
@ -187,166 +200,174 @@ msgstr ""
|
||||||
"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année "
|
"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année "
|
||||||
"suivante avant que les adhérents peuvent renouveler leur adhésion."
|
"suivante avant que les adhérents peuvent renouveler leur adhésion."
|
||||||
|
|
||||||
#: apps/member/models.py:93 apps/note/models/notes.py:135
|
#: apps/member/models.py:94 apps/note/models/notes.py:139
|
||||||
msgid "club"
|
msgid "club"
|
||||||
msgstr "club"
|
msgstr "club"
|
||||||
|
|
||||||
#: apps/member/models.py:94
|
#: apps/member/models.py:95
|
||||||
msgid "clubs"
|
msgid "clubs"
|
||||||
msgstr "clubs"
|
msgstr "clubs"
|
||||||
|
|
||||||
#: apps/member/models.py:117
|
#: apps/member/models.py:118
|
||||||
msgid "role"
|
msgid "role"
|
||||||
msgstr "rôle"
|
msgstr "rôle"
|
||||||
|
|
||||||
#: apps/member/models.py:118
|
#: apps/member/models.py:119
|
||||||
msgid "roles"
|
msgid "roles"
|
||||||
msgstr "rôles"
|
msgstr "rôles"
|
||||||
|
|
||||||
#: apps/member/models.py:142
|
#: apps/member/models.py:143
|
||||||
msgid "membership starts on"
|
msgid "membership starts on"
|
||||||
msgstr "l'adhésion commence le"
|
msgstr "l'adhésion commence le"
|
||||||
|
|
||||||
#: apps/member/models.py:145
|
#: apps/member/models.py:146
|
||||||
msgid "membership ends on"
|
msgid "membership ends on"
|
||||||
msgstr "l'adhésion finie le"
|
msgstr "l'adhésion finie le"
|
||||||
|
|
||||||
#: apps/member/models.py:149
|
#: apps/member/models.py:150
|
||||||
msgid "fee"
|
msgid "fee"
|
||||||
msgstr "cotisation"
|
msgstr "cotisation"
|
||||||
|
|
||||||
#: apps/member/models.py:153
|
#: apps/member/models.py:154
|
||||||
msgid "membership"
|
msgid "membership"
|
||||||
msgstr "adhésion"
|
msgstr "adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:154
|
#: apps/member/models.py:155
|
||||||
msgid "memberships"
|
msgid "memberships"
|
||||||
msgstr "adhésions"
|
msgstr "adhésions"
|
||||||
|
|
||||||
#: apps/member/views.py:63 templates/member/profile_detail.html:42
|
#: apps/member/views.py:69 templates/member/profile_detail.html:46
|
||||||
msgid "Update Profile"
|
msgid "Update Profile"
|
||||||
msgstr "Modifier le profil"
|
msgstr "Modifier le profil"
|
||||||
|
|
||||||
#: apps/member/views.py:79
|
#: apps/member/views.py:82
|
||||||
msgid "An alias with a similar name already exists."
|
msgid "An alias with a similar name already exists."
|
||||||
msgstr "Un alias avec un nom similaire existe déjà."
|
msgstr "Un alias avec un nom similaire existe déjà."
|
||||||
|
|
||||||
#: apps/member/views.py:130
|
#: apps/member/views.py:132
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Account #%(id)s: %(username)s"
|
msgid "Account #%(id)s: %(username)s"
|
||||||
msgstr "Compte n°%(id)s : %(username)s"
|
msgstr "Compte n°%(id)s : %(username)s"
|
||||||
|
|
||||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:93
|
#: apps/member/views.py:202
|
||||||
|
msgid "Alias successfully deleted"
|
||||||
|
msgstr "L'alias a bien été supprimé"
|
||||||
|
|
||||||
|
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
||||||
msgid "source"
|
msgid "source"
|
||||||
msgstr "source"
|
msgstr "source"
|
||||||
|
|
||||||
#: apps/note/admin.py:128 apps/note/admin.py:156
|
#: apps/note/admin.py:128 apps/note/admin.py:156
|
||||||
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:99
|
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:100
|
||||||
msgid "destination"
|
msgid "destination"
|
||||||
msgstr "destination"
|
msgstr "destination"
|
||||||
|
|
||||||
#: apps/note/apps.py:14 apps/note/models/notes.py:54
|
#: apps/note/apps.py:14 apps/note/models/notes.py:58
|
||||||
msgid "note"
|
msgid "note"
|
||||||
msgstr "note"
|
msgstr "note"
|
||||||
|
|
||||||
#: apps/note/forms.py:49
|
#: apps/note/forms.py:20
|
||||||
msgid "Source and destination must be different."
|
msgid "New Alias"
|
||||||
msgstr "La source et la destination doivent être différentes."
|
msgstr "Nouvel alias"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:26
|
#: apps/note/forms.py:25
|
||||||
|
msgid "select an image"
|
||||||
|
msgstr "Choisissez une image"
|
||||||
|
|
||||||
|
#: apps/note/forms.py:26
|
||||||
|
msgid "Maximal size: 2MB"
|
||||||
|
msgstr "Taille maximale : 2 Mo"
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:27
|
||||||
msgid "account balance"
|
msgid "account balance"
|
||||||
msgstr "solde du compte"
|
msgstr "solde du compte"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:27
|
#: apps/note/models/notes.py:28
|
||||||
msgid "in centimes, money credited for this instance"
|
msgid "in centimes, money credited for this instance"
|
||||||
msgstr "en centimes, argent crédité pour cette instance"
|
msgstr "en centimes, argent crédité pour cette instance"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:31
|
#: apps/note/models/notes.py:32
|
||||||
msgid "last negative date"
|
msgid "last negative date"
|
||||||
msgstr "dernier date de négatif"
|
msgstr "dernier date de négatif"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:32
|
#: apps/note/models/notes.py:33
|
||||||
msgid "last time the balance was negative"
|
msgid "last time the balance was negative"
|
||||||
msgstr "dernier instant où la note était en négatif"
|
msgstr "dernier instant où la note était en négatif"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:37
|
#: apps/note/models/notes.py:38
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr "actif"
|
msgstr "actif"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:40
|
#: apps/note/models/notes.py:41
|
||||||
msgid ""
|
msgid ""
|
||||||
"Designates whether this note should be treated as active. Unselect this "
|
"Designates whether this note should be treated as active. Unselect this "
|
||||||
"instead of deleting notes."
|
"instead of deleting notes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Indique si la note est active. Désactiver cela plutôt que supprimer la note."
|
"Indique si la note est active. Désactiver cela plutôt que supprimer la note."
|
||||||
|
|
||||||
#: apps/note/models/notes.py:44
|
#: apps/note/models/notes.py:45
|
||||||
msgid "display image"
|
msgid "display image"
|
||||||
msgstr "image affichée"
|
msgstr "image affichée"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:49 apps/note/models/transactions.py:102
|
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:103
|
||||||
msgid "created at"
|
msgid "created at"
|
||||||
msgstr "créée le"
|
msgstr "créée le"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:55
|
#: apps/note/models/notes.py:59
|
||||||
msgid "notes"
|
msgid "notes"
|
||||||
msgstr "notes"
|
msgstr "notes"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:63
|
#: apps/note/models/notes.py:67
|
||||||
msgid "Note"
|
msgid "Note"
|
||||||
msgstr "Note"
|
msgstr "Note"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:73 apps/note/models/notes.py:97
|
#: apps/note/models/notes.py:77 apps/note/models/notes.py:101
|
||||||
msgid "This alias is already taken."
|
msgid "This alias is already taken."
|
||||||
msgstr "Cet alias est déjà pris."
|
msgstr "Cet alias est déjà pris."
|
||||||
|
|
||||||
#: apps/note/models/notes.py:113
|
#: apps/note/models/notes.py:121
|
||||||
msgid "user"
|
|
||||||
msgstr "utilisateur"
|
|
||||||
|
|
||||||
#: apps/note/models/notes.py:117
|
|
||||||
msgid "one's note"
|
msgid "one's note"
|
||||||
msgstr "note d'un utilisateur"
|
msgstr "note d'un utilisateur"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:118
|
#: apps/note/models/notes.py:122
|
||||||
msgid "users note"
|
msgid "users note"
|
||||||
msgstr "notes des utilisateurs"
|
msgstr "notes des utilisateurs"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:124
|
#: apps/note/models/notes.py:128
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(user)s's note"
|
msgid "%(user)s's note"
|
||||||
msgstr "Note de %(user)s"
|
msgstr "Note de %(user)s"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:139
|
#: apps/note/models/notes.py:143
|
||||||
msgid "club note"
|
msgid "club note"
|
||||||
msgstr "note d'un club"
|
msgstr "note d'un club"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:140
|
#: apps/note/models/notes.py:144
|
||||||
msgid "clubs notes"
|
msgid "clubs notes"
|
||||||
msgstr "notes des clubs"
|
msgstr "notes des clubs"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:146
|
#: apps/note/models/notes.py:150
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Note of %(club)s club"
|
msgid "Note of %(club)s club"
|
||||||
msgstr "Note du club %(club)s"
|
msgstr "Note du club %(club)s"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:166
|
#: apps/note/models/notes.py:170
|
||||||
msgid "special note"
|
msgid "special note"
|
||||||
msgstr "note spéciale"
|
msgstr "note spéciale"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:167
|
#: apps/note/models/notes.py:171
|
||||||
msgid "special notes"
|
msgid "special notes"
|
||||||
msgstr "notes spéciales"
|
msgstr "notes spéciales"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:190
|
#: apps/note/models/notes.py:194
|
||||||
msgid "Invalid alias"
|
msgid "Invalid alias"
|
||||||
msgstr "Alias invalide"
|
msgstr "Alias invalide"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:206
|
#: apps/note/models/notes.py:210
|
||||||
msgid "alias"
|
msgid "alias"
|
||||||
msgstr "alias"
|
msgstr "alias"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:207 templates/member/profile_detail.html:33
|
#: apps/note/models/notes.py:211 templates/member/profile_detail.html:37
|
||||||
msgid "aliases"
|
msgid "aliases"
|
||||||
msgstr "alias"
|
msgstr "alias"
|
||||||
|
|
||||||
|
@ -355,10 +376,10 @@ msgid "Alias is too long."
|
||||||
msgstr "L'alias est trop long."
|
msgstr "L'alias est trop long."
|
||||||
|
|
||||||
#: apps/note/models/notes.py:238
|
#: apps/note/models/notes.py:238
|
||||||
msgid "An alias with a similar name already exists:"
|
msgid "An alias with a similar name already exists: {} "
|
||||||
msgstr "Un alias avec un nom similaire existe déjà."
|
msgstr "Un alias avec un nom similaire existe déjà : {}"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:246
|
#: apps/note/models/notes.py:247
|
||||||
msgid "You can't delete your main alias."
|
msgid "You can't delete your main alias."
|
||||||
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
||||||
|
|
||||||
|
@ -371,11 +392,10 @@ msgid "transaction categories"
|
||||||
msgstr "catégories de transaction"
|
msgstr "catégories de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:47
|
#: apps/note/models/transactions.py:47
|
||||||
#, fuzzy
|
|
||||||
msgid "A template with this name already exist"
|
msgid "A template with this name already exist"
|
||||||
msgstr "Un modèle de transaction avec un nom similaire existe déjà."
|
msgstr "Un modèle de transaction avec un nom similaire existe déjà."
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:109
|
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:111
|
||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr "montant"
|
msgstr "montant"
|
||||||
|
|
||||||
|
@ -383,59 +403,96 @@ msgstr "montant"
|
||||||
msgid "in centimes"
|
msgid "in centimes"
|
||||||
msgstr "en centimes"
|
msgstr "en centimes"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:74
|
#: apps/note/models/transactions.py:75
|
||||||
msgid "transaction template"
|
msgid "transaction template"
|
||||||
msgstr "modèle de transaction"
|
msgstr "modèle de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:75
|
#: apps/note/models/transactions.py:76
|
||||||
msgid "transaction templates"
|
msgid "transaction templates"
|
||||||
msgstr "modèles de transaction"
|
msgstr "modèles de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:106
|
#: apps/note/models/transactions.py:107
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "quantité"
|
msgstr "quantité"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:111
|
#: apps/note/models/transactions.py:117 templates/note/transaction_form.html:15
|
||||||
msgid "reason"
|
msgid "Gift"
|
||||||
msgstr "raison"
|
msgstr "Don"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:115
|
#: apps/note/models/transactions.py:118 templates/base.html:90
|
||||||
msgid "valid"
|
#: templates/note/transaction_form.html:19
|
||||||
msgstr "valide"
|
#: templates/note/transaction_form.html:126
|
||||||
|
msgid "Transfer"
|
||||||
|
msgstr "Virement"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:120
|
#: apps/note/models/transactions.py:119
|
||||||
msgid "transaction"
|
msgid "Template"
|
||||||
msgstr "transaction"
|
msgstr "Bouton"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:121
|
#: apps/note/models/transactions.py:120 templates/note/transaction_form.html:23
|
||||||
msgid "transactions"
|
msgid "Credit"
|
||||||
msgstr "transactions"
|
msgstr "Crédit"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:184
|
#: apps/note/models/transactions.py:121 templates/note/transaction_form.html:27
|
||||||
|
msgid "Debit"
|
||||||
|
msgstr "Retrait"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:122 apps/note/models/transactions.py:230
|
||||||
msgid "membership transaction"
|
msgid "membership transaction"
|
||||||
msgstr "transaction d'adhésion"
|
msgstr "transaction d'adhésion"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:185
|
#: apps/note/models/transactions.py:129
|
||||||
|
msgid "reason"
|
||||||
|
msgstr "raison"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:133
|
||||||
|
msgid "valid"
|
||||||
|
msgstr "valide"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:138
|
||||||
|
msgid "transaction"
|
||||||
|
msgstr "transaction"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:139
|
||||||
|
msgid "transactions"
|
||||||
|
msgstr "transactions"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:207
|
||||||
|
msgid "first_name"
|
||||||
|
msgstr "Prénom"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:212
|
||||||
|
msgid "bank"
|
||||||
|
msgstr "Banque"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:231
|
||||||
msgid "membership transactions"
|
msgid "membership transactions"
|
||||||
msgstr "transactions d'adhésion"
|
msgstr "transactions d'adhésion"
|
||||||
|
|
||||||
#: apps/note/views.py:29
|
#: apps/note/views.py:31
|
||||||
msgid "Transfer money from your account to one or others"
|
msgid "Transfer money"
|
||||||
msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres"
|
msgstr "Transferts d'argent"
|
||||||
|
|
||||||
#: apps/note/views.py:138
|
#: apps/note/views.py:132 templates/base.html:78
|
||||||
msgid "Consommations"
|
msgid "Consumptions"
|
||||||
msgstr "transactions"
|
msgstr "Consommations"
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:155
|
#: note_kfet/settings/__init__.py:61
|
||||||
msgid "German"
|
msgid ""
|
||||||
|
"The Central Authentication Service grants you access to most of our websites "
|
||||||
|
"by authenticating only once, so you don't need to type your credentials "
|
||||||
|
"again unless your session expires or you logout."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:156
|
#: note_kfet/settings/base.py:156
|
||||||
msgid "English"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:157
|
#: note_kfet/settings/base.py:157
|
||||||
|
msgid "English"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: note_kfet/settings/base.py:158
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -443,6 +500,80 @@ msgstr ""
|
||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
||||||
|
|
||||||
|
#: templates/base.html:81
|
||||||
|
msgid "Clubs"
|
||||||
|
msgstr "Clubs"
|
||||||
|
|
||||||
|
#: templates/base.html:84
|
||||||
|
msgid "Activities"
|
||||||
|
msgstr "Activités"
|
||||||
|
|
||||||
|
#: templates/base.html:87
|
||||||
|
msgid "Buttons"
|
||||||
|
msgstr "Boutons"
|
||||||
|
|
||||||
|
#: templates/cas_server/base.html:7
|
||||||
|
msgid "Central Authentication Service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/base.html:43
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"A new version of the application is available. This instance runs "
|
||||||
|
"%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
|
||||||
|
"upgrading."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:4
|
||||||
|
msgid ""
|
||||||
|
"<h3>Log In Successful</h3>You have successfully logged into the Central "
|
||||||
|
"Authentication Service.<br/>For security reasons, please Log Out and Exit "
|
||||||
|
"your web browser when you are done accessing services that require "
|
||||||
|
"authentication!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:8
|
||||||
|
msgid "Log me out from all my sessions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:14
|
||||||
|
msgid "Forget the identity provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/logged.html:18
|
||||||
|
msgid "Logout"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:6
|
||||||
|
msgid "Please log in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:11
|
||||||
|
msgid ""
|
||||||
|
"If you don't have any Note Kfet account, please follow <a href='/accounts/"
|
||||||
|
"signup'>this link to sign up</a>."
|
||||||
|
msgstr ""
|
||||||
|
"Si vous n'avez pas de compte Note Kfet, veuillez suivre <a href='/accounts/"
|
||||||
|
"signup'>ce lien pour vous inscrire</a>."
|
||||||
|
|
||||||
|
#: templates/cas_server/login.html:17
|
||||||
|
msgid "Login"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/cas_server/warn.html:9
|
||||||
|
msgid "Connect to the service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/django_filters/rest_framework/crispy_form.html:4
|
||||||
|
#: templates/django_filters/rest_framework/form.html:2
|
||||||
|
msgid "Field filters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/django_filters/rest_framework/form.html:5
|
||||||
|
#: templates/member/club_form.html:10
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
#: templates/member/club_detail.html:10
|
#: templates/member/club_detail.html:10
|
||||||
msgid "Membership starts on"
|
msgid "Membership starts on"
|
||||||
msgstr "L'adhésion commence le"
|
msgstr "L'adhésion commence le"
|
||||||
|
@ -455,10 +586,22 @@ msgstr "L'adhésion finie le"
|
||||||
msgid "Membership duration"
|
msgid "Membership duration"
|
||||||
msgstr "Durée de l'adhésion"
|
msgstr "Durée de l'adhésion"
|
||||||
|
|
||||||
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:30
|
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34
|
||||||
msgid "balance"
|
msgid "balance"
|
||||||
msgstr "solde du compte"
|
msgstr "solde du compte"
|
||||||
|
|
||||||
|
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75
|
||||||
|
msgid "Transaction history"
|
||||||
|
msgstr "Historique des transactions"
|
||||||
|
|
||||||
|
#: templates/member/club_form.html:6
|
||||||
|
msgid "Clubs list"
|
||||||
|
msgstr "Liste des clubs"
|
||||||
|
|
||||||
|
#: templates/member/club_list.html:8
|
||||||
|
msgid "New club"
|
||||||
|
msgstr "Nouveau club"
|
||||||
|
|
||||||
#: templates/member/manage_auth_tokens.html:16
|
#: templates/member/manage_auth_tokens.html:16
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Jeton"
|
msgstr "Jeton"
|
||||||
|
@ -471,33 +614,35 @@ msgstr "Créé le"
|
||||||
msgid "Regenerate token"
|
msgid "Regenerate token"
|
||||||
msgstr "Regénérer le jeton"
|
msgstr "Regénérer le jeton"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:11
|
#: templates/member/profile_alias.html:10
|
||||||
|
msgid "Add alias"
|
||||||
|
msgstr "Ajouter un alias"
|
||||||
|
|
||||||
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "first name"
|
msgid "first name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:14
|
#: templates/member/profile_detail.html:18
|
||||||
msgid "username"
|
msgid "username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:17
|
#: templates/member/profile_detail.html:21
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Change password"
|
|
||||||
msgid "password"
|
msgid "password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:20
|
#: templates/member/profile_detail.html:24
|
||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr "Changer le mot de passe"
|
msgstr "Changer le mot de passe"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:38
|
#: templates/member/profile_detail.html:42
|
||||||
msgid "Manage auth token"
|
msgid "Manage auth token"
|
||||||
msgstr "Gérer les jetons d'authentification"
|
msgstr "Gérer les jetons d'authentification"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:51
|
#: templates/member/profile_detail.html:49
|
||||||
msgid "Transaction history"
|
msgid "View Profile"
|
||||||
msgstr "Historique des transactions"
|
msgstr "Voir le profil"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:54
|
#: templates/member/profile_detail.html:62
|
||||||
msgid "View my memberships"
|
msgid "View my memberships"
|
||||||
msgstr "Voir mes adhésions"
|
msgstr "Voir mes adhésions"
|
||||||
|
|
||||||
|
@ -505,13 +650,88 @@ msgstr "Voir mes adhésions"
|
||||||
msgid "Save Changes"
|
msgid "Save Changes"
|
||||||
msgstr "Sauvegarder les changements"
|
msgstr "Sauvegarder les changements"
|
||||||
|
|
||||||
|
#: templates/member/signup.html:5 templates/member/signup.html:8
|
||||||
#: templates/member/signup.html:14
|
#: templates/member/signup.html:14
|
||||||
msgid "Sign Up"
|
msgid "Sign up"
|
||||||
msgstr ""
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:35
|
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:38
|
||||||
msgid "Transfer"
|
msgid "Select emitters"
|
||||||
msgstr "Virement"
|
msgstr "Sélection des émetteurs"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:45
|
||||||
|
msgid "Select consumptions"
|
||||||
|
msgstr "Consommations"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:51
|
||||||
|
msgid "Consume!"
|
||||||
|
msgstr "Consommer !"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:64
|
||||||
|
msgid "Most used buttons"
|
||||||
|
msgstr "Boutons les plus utilisés"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:121
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr "Éditer"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:126
|
||||||
|
msgid "Single consumptions"
|
||||||
|
msgstr "Consos simples"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:130
|
||||||
|
msgid "Double consumptions"
|
||||||
|
msgstr "Consos doubles"
|
||||||
|
|
||||||
|
#: templates/note/conso_form.html:141
|
||||||
|
msgid "Recent transactions history"
|
||||||
|
msgstr "Historique des transactions récentes"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:55
|
||||||
|
msgid "External payment"
|
||||||
|
msgstr "Paiement extérieur"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:63
|
||||||
|
msgid "Transfer type"
|
||||||
|
msgstr "Type de transfert"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:73
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:79
|
||||||
|
msgid "First name"
|
||||||
|
msgstr "Prénom"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:85
|
||||||
|
msgid "Bank"
|
||||||
|
msgstr "Banque"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:97
|
||||||
|
#: templates/note/transaction_form.html:179
|
||||||
|
#: templates/note/transaction_form.html:186
|
||||||
|
msgid "Select receivers"
|
||||||
|
msgstr "Sélection des destinataires"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:114
|
||||||
|
msgid "Amount"
|
||||||
|
msgstr "Montant"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:119
|
||||||
|
msgid "Reason"
|
||||||
|
msgstr "Raison"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:193
|
||||||
|
msgid "Credit note"
|
||||||
|
msgstr "Note à créditer"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:200
|
||||||
|
msgid "Debit note"
|
||||||
|
msgstr "Note à débiter"
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_form.html:6
|
||||||
|
msgid "Buttons list"
|
||||||
|
msgstr "Liste des boutons"
|
||||||
|
|
||||||
#: templates/registration/logged_out.html:8
|
#: templates/registration/logged_out.html:8
|
||||||
msgid "Thanks for spending some quality time with the Web site today."
|
msgid "Thanks for spending some quality time with the Web site today."
|
||||||
|
@ -522,7 +742,7 @@ msgid "Log in again"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:7 templates/registration/login.html:8
|
#: templates/registration/login.html:7 templates/registration/login.html:8
|
||||||
#: templates/registration/login.html:22
|
#: templates/registration/login.html:26
|
||||||
#: templates/registration/password_reset_complete.html:10
|
#: templates/registration/password_reset_complete.html:10
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -534,7 +754,7 @@ msgid ""
|
||||||
"page. Would you like to login to a different account?"
|
"page. Would you like to login to a different account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:23
|
#: templates/registration/login.html:27
|
||||||
msgid "Forgotten your password or username?"
|
msgid "Forgotten your password or username?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ server {
|
||||||
# the port your site will be served on
|
# the port your site will be served on
|
||||||
listen 80;
|
listen 80;
|
||||||
# the domain name it will serve for
|
# the domain name it will serve for
|
||||||
server_name note.comby.xyz; # substitute your machine's IP address or FQDN
|
server_name note.example.org; # substitute your machine's IP address or FQDN
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
|
|
||||||
# max upload size
|
# max upload size
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "cas_server.servicepattern",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"pos": 1,
|
||||||
|
"pattern": ".*",
|
||||||
|
"name": "REPLACEME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -6,14 +6,5 @@
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"name": "La Note Kfet \ud83c\udf7b"
|
"name": "La Note Kfet \ud83c\udf7b"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "cas_server.servicepattern",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"pos": 1,
|
|
||||||
"pattern": ".*",
|
|
||||||
"name": "REPLACEME"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
|
|
||||||
from urllib.parse import urlencode, parse_qs, urlsplit, urlunsplit
|
|
||||||
|
|
||||||
|
|
||||||
class TurbolinksMiddleware(object):
|
class TurbolinksMiddleware(object):
|
||||||
"""
|
"""
|
||||||
|
@ -35,4 +31,3 @@ class TurbolinksMiddleware(object):
|
||||||
location = request.session.pop('_turbolinks_redirect_to')
|
location = request.session.pop('_turbolinks_redirect_to')
|
||||||
response['Turbolinks-Location'] = location
|
response['Turbolinks-Location'] = location
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import os
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .base import *
|
from .base import *
|
||||||
|
|
||||||
|
|
||||||
def read_env():
|
def read_env():
|
||||||
"""Pulled from Honcho code with minor updates, reads local default
|
"""Pulled from Honcho code with minor updates, reads local default
|
||||||
environment variables from a .env file located in the project root
|
environment variables from a .env file located in the project root
|
||||||
|
@ -25,22 +29,53 @@ def read_env():
|
||||||
val = re.sub(r'\\(.)', r'\1', m3.group(1))
|
val = re.sub(r'\\(.)', r'\1', m3.group(1))
|
||||||
os.environ.setdefault(key, val)
|
os.environ.setdefault(key, val)
|
||||||
|
|
||||||
|
|
||||||
read_env()
|
read_env()
|
||||||
|
|
||||||
app_stage = os.environ.get('DJANGO_APP_STAGE', 'dev')
|
app_stage = os.environ.get('DJANGO_APP_STAGE', 'dev')
|
||||||
if app_stage == 'prod':
|
if app_stage == 'prod':
|
||||||
from .production import *
|
from .production import *
|
||||||
DATABASES["default"]["PASSWORD"] = os.environ.get('DJANGO_DB_PASSWORD','CHANGE_ME_IN_ENV_SETTINGS')
|
|
||||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY','CHANGE_ME_IN_ENV_SETTINGS')
|
|
||||||
ALLOWED_HOSTS.append(os.environ.get('ALLOWED_HOSTS','localhost'))
|
|
||||||
else:
|
else:
|
||||||
from .development import *
|
from .development import *
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
#in secrets.py defines everything you want
|
||||||
from .secrets import *
|
from .secrets import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# env variables set at the of in /env/bin/activate
|
if "cas" in INSTALLED_APPS:
|
||||||
# don't forget to unset in deactivate !
|
MIDDLEWARE += ['cas.middleware.CASMiddleware']
|
||||||
|
# CAS Settings
|
||||||
|
CAS_SERVER_URL = "https://" + os.getenv("NOTE_URL", "note.example.com") + "/cas/"
|
||||||
|
CAS_AUTO_CREATE_USER = False
|
||||||
|
CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
|
||||||
|
CAS_FAVICON_URL = "/static/favicon/favicon-32x32.png"
|
||||||
|
CAS_SHOW_SERVICE_MESSAGES = True
|
||||||
|
CAS_SHOW_POWERED = False
|
||||||
|
CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False
|
||||||
|
CAS_PROVIDE_URL_TO_LOGOUT = True
|
||||||
|
CAS_INFO_MESSAGES = {
|
||||||
|
"cas_explained": {
|
||||||
|
"message": _(
|
||||||
|
u"The Central Authentication Service grants you access to most of our websites by "
|
||||||
|
u"authenticating only once, so you don't need to type your credentials again unless "
|
||||||
|
u"your session expires or you logout."
|
||||||
|
),
|
||||||
|
"discardable": True,
|
||||||
|
"type": "info", # one of info, success, info, warning, danger
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
CAS_INFO_MESSAGES_ORDER = [
|
||||||
|
'cas_explained',
|
||||||
|
]
|
||||||
|
AUTHENTICATION_BACKENDS += ('cas.backends.CASBackend',)
|
||||||
|
|
||||||
|
|
||||||
|
if "logs" in INSTALLED_APPS:
|
||||||
|
MIDDLEWARE += ('logs.middlewares.LogsMiddleware',)
|
||||||
|
|
||||||
|
if "debug_toolbar" in INSTALLED_APPS:
|
||||||
|
MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")
|
||||||
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
|
@ -37,9 +37,10 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
# External apps
|
# External apps
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
'reversion',
|
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
|
'cas_server',
|
||||||
|
'cas',
|
||||||
# Django contrib
|
# Django contrib
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
|
@ -55,9 +56,6 @@ INSTALLED_APPS = [
|
||||||
# Autocomplete
|
# Autocomplete
|
||||||
'dal',
|
'dal',
|
||||||
'dal_select2',
|
'dal_select2',
|
||||||
# CAS
|
|
||||||
'cas_server',
|
|
||||||
'cas',
|
|
||||||
|
|
||||||
# Note apps
|
# Note apps
|
||||||
'activity',
|
'activity',
|
||||||
|
@ -81,7 +79,6 @@ MIDDLEWARE = [
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
||||||
'note_kfet.middlewares.TurbolinksMiddleware',
|
'note_kfet.middlewares.TurbolinksMiddleware',
|
||||||
'cas.middleware.CASMiddleware',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'note_kfet.urls'
|
ROOT_URLCONF = 'note_kfet.urls'
|
||||||
|
@ -98,7 +95,7 @@ TEMPLATES = [
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
# 'django.template.context_processors.media',
|
# 'django.template.context_processors.media',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -133,7 +130,7 @@ PASSWORD_HASHERS = [
|
||||||
# Django Guardian object permissions
|
# Django Guardian object permissions
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
#'django.contrib.auth.backends.ModelBackend', # this is default
|
# 'django.contrib.auth.backends.ModelBackend', # this is default
|
||||||
'member.backends.PermissionBackend',
|
'member.backends.PermissionBackend',
|
||||||
'cas.backends.CASBackend',
|
'cas.backends.CASBackend',
|
||||||
)
|
)
|
||||||
|
@ -146,12 +143,13 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||||
],
|
],
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
]
|
],
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
|
'PAGE_SIZE': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
ANONYMOUS_USER_NAME = None # Disable guardian anonymous user
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||||
|
|
||||||
|
@ -182,7 +180,7 @@ FIXTURE_DIRS = [os.path.join(BASE_DIR, "note_kfet/fixtures")]
|
||||||
# Don't put anything in this directory yourself; store your static files
|
# Don't put anything in this directory yourself; store your static files
|
||||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||||
# Example: "/var/www/example.com/static/"
|
# Example: "/var/www/example.com/static/"
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR,"static/")
|
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
|
||||||
# STATICFILES_DIRS = [
|
# STATICFILES_DIRS = [
|
||||||
# os.path.join(BASE_DIR, 'static')]
|
# os.path.join(BASE_DIR, 'static')]
|
||||||
STATICFILES_DIRS = []
|
STATICFILES_DIRS = []
|
||||||
|
@ -194,15 +192,9 @@ STATIC_URL = '/static/'
|
||||||
|
|
||||||
ALIAS_VALIDATOR_REGEX = r''
|
ALIAS_VALIDATOR_REGEX = r''
|
||||||
|
|
||||||
MEDIA_ROOT=os.path.join(BASE_DIR,"media")
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
MEDIA_URL='/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
# Profile Picture Settings
|
# Profile Picture Settings
|
||||||
PIC_WIDTH = 200
|
PIC_WIDTH = 200
|
||||||
PIC_RATIO = 1
|
PIC_RATIO = 1
|
||||||
|
|
||||||
# CAS Settings
|
|
||||||
CAS_AUTO_CREATE_USER = False
|
|
||||||
CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
|
|
||||||
CAS_FAVICON_URL = "/static/favicon/favicon-32x32.png"
|
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,30 @@
|
||||||
# - and more ...
|
# - and more ...
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
||||||
from . import *
|
from . import *
|
||||||
import os
|
|
||||||
|
|
||||||
DATABASES = {
|
if os.getenv("DJANGO_DEV_STORE_METHOD", "sqllite") == "postgresql":
|
||||||
'default': {
|
DATABASES = {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'default': {
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': os.environ.get('DJANGO_DB_NAME', 'note_db'),
|
||||||
|
'USER': os.environ.get('DJANGO_DB_USER', 'note'),
|
||||||
|
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
|
||||||
|
'HOST': os.environ.get('DJANGO_DB_HOST', 'localhost'),
|
||||||
|
'PORT': os.environ.get('DJANGO_DB_PORT', ''), # Use default port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# Break it, fix it!
|
# Break it, fix it!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -38,7 +51,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
# EMAIL_HOST_USER = 'change_me'
|
# EMAIL_HOST_USER = 'change_me'
|
||||||
# EMAIL_HOST_PASSWORD = 'change_me'
|
# EMAIL_HOST_PASSWORD = 'change_me'
|
||||||
|
|
||||||
SERVER_EMAIL = 'no-reply@example.org'
|
SERVER_EMAIL = 'no-reply@' + os.getenv("DOMAIN", "example.com")
|
||||||
|
|
||||||
# Security settings
|
# Security settings
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = False
|
SECURE_CONTENT_TYPE_NOSNIFF = False
|
||||||
|
@ -51,4 +64,8 @@ SESSION_COOKIE_AGE = 60 * 60 * 3
|
||||||
|
|
||||||
# CAS Client settings
|
# CAS Client settings
|
||||||
# Can be modified in secrets.py
|
# Can be modified in secrets.py
|
||||||
CAS_SERVER_URL = "https://note.comby.xyz/cas/"
|
CAS_SERVER_URL = "http://localhost:8000/cas/"
|
||||||
|
|
||||||
|
STATIC_ROOT = '' # not needed in development settings
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, 'static')]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Production Settings #
|
# Production Settings #
|
||||||
########################
|
########################
|
||||||
|
@ -14,11 +16,11 @@
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
'NAME': 'note_db',
|
'NAME': os.environ.get('DJANGO_DB_NAME', 'note_db'),
|
||||||
'USER': 'note',
|
'USER': os.environ.get('DJANGO_DB_USER', 'note'),
|
||||||
'PASSWORD': 'update_in_env_variable',
|
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
|
||||||
'HOST': '127.0.0.1',
|
'HOST': os.environ.get('DJANGO_DB_HOST', 'localhost'),
|
||||||
'PORT': '',
|
'PORT': os.environ.get('DJANGO_DB_PORT', ''), # Use default port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +28,9 @@ DATABASES = {
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# Mandatory !
|
# Mandatory !
|
||||||
ALLOWED_HOSTS = ['127.0.0.1','note.comby.xyz']
|
ALLOWED_HOSTS = [os.environ.get('NOTE_URL', 'localhost')]
|
||||||
|
|
||||||
|
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
@ -37,7 +41,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
# EMAIL_HOST_USER = 'change_me'
|
# EMAIL_HOST_USER = 'change_me'
|
||||||
# EMAIL_HOST_PASSWORD = 'change_me'
|
# EMAIL_HOST_PASSWORD = 'change_me'
|
||||||
|
|
||||||
SERVER_EMAIL = 'no-reply@example.org'
|
SERVER_EMAIL = 'no-reply@' + os.getenv("DOMAIN", "example.com")
|
||||||
|
|
||||||
# Security settings
|
# Security settings
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = False
|
SECURE_CONTENT_TYPE_NOSNIFF = False
|
||||||
|
@ -49,4 +53,4 @@ X_FRAME_OPTIONS = 'DENY'
|
||||||
SESSION_COOKIE_AGE = 60 * 60 * 3
|
SESSION_COOKIE_AGE = 60 * 60 * 3
|
||||||
|
|
||||||
# CAS Client settings
|
# CAS Client settings
|
||||||
CAS_SERVER_URL = "https://note.crans.org/cas/"
|
CAS_SERVER_URL = "https://" + os.getenv("NOTE_URL", "note.example.com") + "/cas/"
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
app_name = 'logs'
|
# CAS
|
||||||
|
OPTIONAL_APPS = [
|
||||||
# TODO User interface
|
# 'cas_server',
|
||||||
urlpatterns = [
|
# 'cas',
|
||||||
|
# 'debug_toolbar'
|
||||||
]
|
]
|
|
@ -1,13 +1,11 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from django.conf.urls.static import static
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from cas import views as cas_views
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Dev so redirect to something random
|
# Dev so redirect to something random
|
||||||
|
@ -16,25 +14,34 @@ urlpatterns = [
|
||||||
# Include project routers
|
# Include project routers
|
||||||
path('note/', include('note.urls')),
|
path('note/', include('note.urls')),
|
||||||
|
|
||||||
# Include CAS Client routers
|
|
||||||
path('accounts/login/', cas_views.login, name='login'),
|
|
||||||
path('accounts/logout/', cas_views.logout, name='logout'),
|
|
||||||
|
|
||||||
# Include Django Contrib and Core routers
|
# Include Django Contrib and Core routers
|
||||||
path('i18n/', include('django.conf.urls.i18n')),
|
path('i18n/', include('django.conf.urls.i18n')),
|
||||||
path('accounts/', include('member.urls')),
|
path('accounts/', include('member.urls')),
|
||||||
path('accounts/', include('django.contrib.auth.urls')),
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
# Include CAS Server routers
|
|
||||||
path('cas/', include('cas_server.urls', namespace="cas_server")),
|
|
||||||
|
|
||||||
# Include Django REST API
|
|
||||||
path('api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
|
|
||||||
path('logs/', include('logs.urls')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
if "cas_server" in settings.INSTALLED_APPS:
|
||||||
|
urlpatterns += [
|
||||||
|
# Include CAS Server routers
|
||||||
|
path('cas/', include('cas_server.urls', namespace="cas_server")),
|
||||||
|
]
|
||||||
|
if "cas" in settings.INSTALLED_APPS:
|
||||||
|
from cas import views as cas_views
|
||||||
|
urlpatterns += [
|
||||||
|
# Include CAS Client routers
|
||||||
|
path('accounts/login/cas/', cas_views.login, name='cas_login'),
|
||||||
|
path('accounts/logout/cas/', cas_views.logout, name='cas_logout'),
|
||||||
|
|
||||||
|
]
|
||||||
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
|
import debug_toolbar
|
||||||
|
urlpatterns = [
|
||||||
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
] + urlpatterns
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
djangorestframework==3.9.0
|
||||||
|
django-rest-polymorphic==0.1.8
|
||||||
|
|
|
@ -4,18 +4,12 @@ defusedxml==0.6.0
|
||||||
Django~=2.2
|
Django~=2.2
|
||||||
django-allauth==0.39.1
|
django-allauth==0.39.1
|
||||||
django-autocomplete-light==3.5.1
|
django-autocomplete-light==3.5.1
|
||||||
django-cas-client==1.5.3
|
|
||||||
django-cas-server==1.1.0
|
|
||||||
django-crispy-forms==1.7.2
|
django-crispy-forms==1.7.2
|
||||||
django-extensions==2.1.9
|
django-extensions==2.1.9
|
||||||
django-filter==2.2.0
|
django-filter==2.2.0
|
||||||
django-polymorphic==2.0.3
|
django-polymorphic==2.0.3
|
||||||
djangorestframework==3.9.0
|
|
||||||
django-rest-polymorphic==0.1.8
|
|
||||||
django-reversion==3.0.3
|
|
||||||
django-tables2==2.1.0
|
django-tables2==2.1.0
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
psycopg2==2.8.4
|
|
||||||
idna==2.8
|
idna==2.8
|
||||||
oauthlib==3.1.0
|
oauthlib==3.1.0
|
||||||
Pillow==6.1.0
|
Pillow==6.1.0
|
|
@ -0,0 +1,2 @@
|
||||||
|
django-cas-client==1.5.3
|
||||||
|
django-cas-server==1.1.0
|
|
@ -0,0 +1 @@
|
||||||
|
psycopg2==2.8.4
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert balance in cents to a human readable amount
|
||||||
|
* @param value the balance, in cents
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function pretty_money(value) {
|
||||||
|
if (value % 100 === 0)
|
||||||
|
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + " €";
|
||||||
|
else
|
||||||
|
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "."
|
||||||
|
+ (Math.abs(value) % 100 < 10 ? "0" : "") + (Math.abs(value) % 100) + " €";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a message on the top of the page.
|
||||||
|
* @param msg The message to display
|
||||||
|
* @param alert_type The type of the alert. Choices: info, success, warning, danger
|
||||||
|
*/
|
||||||
|
function addMsg(msg, alert_type) {
|
||||||
|
let msgDiv = $("#messages");
|
||||||
|
let html = msgDiv.html();
|
||||||
|
html += "<div class=\"alert alert-" + alert_type + " alert-dismissible\">" +
|
||||||
|
"<button class=\"close\" data-dismiss=\"alert\" href=\"#\"><span aria-hidden=\"true\">×</span></button>"
|
||||||
|
+ msg + "</div>\n";
|
||||||
|
msgDiv.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the balance of the user on the right top corner
|
||||||
|
*/
|
||||||
|
function refreshBalance() {
|
||||||
|
$("#user_balance").load("/ #user_balance");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the 20 first matched notes with a given pattern
|
||||||
|
* @param pattern The pattern that is queried
|
||||||
|
* @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
|
||||||
|
*/
|
||||||
|
function getMatchedNotes(pattern, fun) {
|
||||||
|
$.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club&ordering=normalized_name", fun);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a <li> entry with a given id and text
|
||||||
|
*/
|
||||||
|
function li(id, text) {
|
||||||
|
return "<li class=\"list-group-item py-1 d-flex justify-content-between align-items-center\"" +
|
||||||
|
" id=\"" + id + "\">" + text + "</li>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render note name and picture
|
||||||
|
* @param note The note to render
|
||||||
|
* @param alias The alias to be displayed
|
||||||
|
* @param user_note_field
|
||||||
|
* @param profile_pic_field
|
||||||
|
*/
|
||||||
|
function displayNote(note, alias, user_note_field=null, profile_pic_field=null) {
|
||||||
|
let img = note == null ? null : note.display_image;
|
||||||
|
if (img == null)
|
||||||
|
img = '/media/pic/default.png';
|
||||||
|
if (note !== null && alias !== note.name)
|
||||||
|
alias += " (aka. " + note.name + ")";
|
||||||
|
if (note !== null && user_note_field !== null)
|
||||||
|
$("#" + user_note_field).text(alias + " : " + pretty_money(note.balance));
|
||||||
|
if (profile_pic_field != null)
|
||||||
|
$("#" + profile_pic_field).attr('src', img);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a note from the emitters.
|
||||||
|
* @param d The note to remove
|
||||||
|
* @param note_prefix The prefix of the identifiers of the <li> blocks of the emitters
|
||||||
|
* @param notes_display An array containing the infos of the buyers: [alias, note id, note object, quantity]
|
||||||
|
* @param note_list_id The div block identifier where the notes of the buyers are displayed
|
||||||
|
* @param user_note_field The identifier of the field that display the note of the hovered note (useful in
|
||||||
|
* consumptions, put null if not used)
|
||||||
|
* @param profile_pic_field The identifier of the field that display the profile picture of the hovered note
|
||||||
|
* (useful in consumptions, put null if not used)
|
||||||
|
* @returns an anonymous function to be compatible with jQuery events
|
||||||
|
*/
|
||||||
|
function removeNote(d, note_prefix="note", notes_display, note_list_id, user_note_field=null, profile_pic_field=null) {
|
||||||
|
return (function() {
|
||||||
|
let new_notes_display = [];
|
||||||
|
let html = "";
|
||||||
|
notes_display.forEach(function (disp) {
|
||||||
|
if (disp.quantity > 1 || disp.id !== d.id) {
|
||||||
|
disp.quantity -= disp.id === d.id ? 1 : 0;
|
||||||
|
new_notes_display.push(disp);
|
||||||
|
html += li(note_prefix + "_" + disp.id, disp.name
|
||||||
|
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notes_display.length = 0;
|
||||||
|
new_notes_display.forEach(function(disp) {
|
||||||
|
notes_display.push(disp);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#" + note_list_id).html(html);
|
||||||
|
notes_display.forEach(function (disp) {
|
||||||
|
let obj = $("#" + note_prefix + "_" + disp.id);
|
||||||
|
obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field));
|
||||||
|
obj.hover(function() {
|
||||||
|
if (disp.note)
|
||||||
|
displayNote(disp.note, disp.name, user_note_field, profile_pic_field);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an auto-complete field to query a note with its alias
|
||||||
|
* @param field_id The identifier of the text field where the alias is typed
|
||||||
|
* @param alias_matched_id The div block identifier where the matched aliases are displayed
|
||||||
|
* @param note_list_id The div block identifier where the notes of the buyers are displayed
|
||||||
|
* @param notes An array containing the note objects of the buyers
|
||||||
|
* @param notes_display An array containing the infos of the buyers: [alias, note id, note object, quantity]
|
||||||
|
* @param alias_prefix The prefix of the <li> blocks for the matched aliases
|
||||||
|
* @param note_prefix The prefix of the <li> blocks for the notes of the buyers
|
||||||
|
* @param user_note_field The identifier of the field that display the note of the hovered note (useful in
|
||||||
|
* consumptions, put null if not used)
|
||||||
|
* @param profile_pic_field The identifier of the field that display the profile picture of the hovered note
|
||||||
|
* (useful in consumptions, put null if not used)
|
||||||
|
* @param alias_click Function that is called when an alias is clicked. If this method exists and doesn't return true,
|
||||||
|
* the associated note is not displayed.
|
||||||
|
* Useful for a consumption if the item is selected before.
|
||||||
|
*/
|
||||||
|
function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes_display, alias_prefix="alias",
|
||||||
|
note_prefix="note", user_note_field=null, profile_pic_field=null, alias_click=null) {
|
||||||
|
let field = $("#" + field_id);
|
||||||
|
// When the user clicks on the search field, it is immediately cleared
|
||||||
|
field.click(function() {
|
||||||
|
field.val("");
|
||||||
|
});
|
||||||
|
|
||||||
|
let old_pattern = null;
|
||||||
|
|
||||||
|
// When the user type "Enter", the first alias is clicked
|
||||||
|
field.keypress(function(event) {
|
||||||
|
if (event.originalEvent.charCode === 13)
|
||||||
|
$("#" + alias_matched_id + " li").first().trigger("click");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the user type something, the matched aliases are refreshed
|
||||||
|
field.keyup(function(e) {
|
||||||
|
if (e.originalEvent.charCode === 13)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let pattern = field.val();
|
||||||
|
// If the pattern is not modified, we don't query the API
|
||||||
|
if (pattern === old_pattern || pattern === "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
old_pattern = pattern;
|
||||||
|
|
||||||
|
// Clear old matched notes
|
||||||
|
notes.length = 0;
|
||||||
|
|
||||||
|
let aliases_matched_obj = $("#" + alias_matched_id);
|
||||||
|
let aliases_matched_html = "";
|
||||||
|
|
||||||
|
// Get matched notes with the given pattern
|
||||||
|
getMatchedNotes(pattern, function(aliases) {
|
||||||
|
// The response arrived too late, we stop the request
|
||||||
|
if (pattern !== $("#" + field_id).val())
|
||||||
|
return;
|
||||||
|
|
||||||
|
aliases.results.forEach(function (alias) {
|
||||||
|
let note = alias.note;
|
||||||
|
aliases_matched_html += li(alias_prefix + "_" + alias.id, alias.name);
|
||||||
|
note.alias = alias;
|
||||||
|
notes.push(note);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display the list of matched aliases
|
||||||
|
aliases_matched_obj.html(aliases_matched_html);
|
||||||
|
|
||||||
|
notes.forEach(function (note) {
|
||||||
|
let alias = note.alias;
|
||||||
|
let alias_obj = $("#" + alias_prefix + "_" + alias.id);
|
||||||
|
// When an alias is hovered, the profile picture and the balance are displayed at the right place
|
||||||
|
alias_obj.hover(function () {
|
||||||
|
displayNote(note, alias.name, user_note_field, profile_pic_field);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the user click on an alias, the associated note is added to the emitters
|
||||||
|
alias_obj.click(function () {
|
||||||
|
field.val("");
|
||||||
|
// If the note is already an emitter, we increase the quantity
|
||||||
|
var disp = null;
|
||||||
|
notes_display.forEach(function (d) {
|
||||||
|
// We compare the note ids
|
||||||
|
if (d.id === note.id) {
|
||||||
|
d.quantity += 1;
|
||||||
|
disp = d;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// In the other case, we add a new emitter
|
||||||
|
if (disp == null) {
|
||||||
|
disp = {
|
||||||
|
name: alias.name,
|
||||||
|
id: note.id,
|
||||||
|
note: note,
|
||||||
|
quantity: 1
|
||||||
|
};
|
||||||
|
notes_display.push(disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the function alias_click exists, it is called. If it doesn't return true, then the notes are
|
||||||
|
// note displayed. Useful for a consumption when a button is already clicked
|
||||||
|
if (alias_click && !alias_click())
|
||||||
|
return;
|
||||||
|
|
||||||
|
let note_list = $("#" + note_list_id);
|
||||||
|
let html = "";
|
||||||
|
notes_display.forEach(function (disp) {
|
||||||
|
html += li(note_prefix + "_" + disp.id, disp.name
|
||||||
|
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emitters are displayed
|
||||||
|
note_list.html(html);
|
||||||
|
|
||||||
|
notes_display.forEach(function (disp) {
|
||||||
|
let line_obj = $("#" + note_prefix + "_" + disp.id);
|
||||||
|
// Hover an emitter display also the profile picture
|
||||||
|
line_obj.hover(function () {
|
||||||
|
displayNote(disp.note, disp.name, user_note_field, profile_pic_field);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When an emitter is clicked, it is removed
|
||||||
|
line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field,
|
||||||
|
profile_pic_field));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a validate button is clicked, we switch the validation status
|
||||||
|
function de_validate(id, validated) {
|
||||||
|
$("#validate_" + id).html("<strong style=\"font-size: 16pt;\">⟳ ...</strong>");
|
||||||
|
|
||||||
|
// Perform a PATCH request to the API in order to update the transaction
|
||||||
|
// If the user has insuffisent rights, an error message will appear
|
||||||
|
$.ajax({
|
||||||
|
"url": "/api/note/transaction/transaction/" + id + "/",
|
||||||
|
type: "PATCH",
|
||||||
|
dataType: "json",
|
||||||
|
headers: {
|
||||||
|
"X-CSRFTOKEN": CSRF_TOKEN
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
"resourcetype": "TemplateTransaction",
|
||||||
|
valid: !validated
|
||||||
|
},
|
||||||
|
success: function () {
|
||||||
|
// Refresh jQuery objects
|
||||||
|
$(".validate").click(de_validate);
|
||||||
|
|
||||||
|
refreshBalance();
|
||||||
|
// error if this method doesn't exist. Please define it.
|
||||||
|
refreshHistory();
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
addMsg("Une erreur est survenue lors de la validation/dévalidation " +
|
||||||
|
"de cette transaction : " + err.responseText, "danger");
|
||||||
|
|
||||||
|
refreshBalance();
|
||||||
|
// error if this method doesn't exist. Please define it.
|
||||||
|
refreshHistory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the history table on the consumptions page.
|
||||||
|
*/
|
||||||
|
function refreshHistory() {
|
||||||
|
$("#history").load("/note/consos/ #history");
|
||||||
|
$("#most_used").load("/note/consos/ #most_used");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// If hash of a category in the URL, then select this category
|
||||||
|
// else select the first one
|
||||||
|
if (location.hash) {
|
||||||
|
$("a[href='" + location.hash + "']").tab("show");
|
||||||
|
} else {
|
||||||
|
$("a[data-toggle='tab']").first().tab("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When selecting a category, change URL
|
||||||
|
$(document.body).on("click", "a[data-toggle='tab']", function() {
|
||||||
|
location.hash = this.getAttribute("href");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switching in double consumptions mode should update the layout
|
||||||
|
let double_conso_obj = $("#double_conso");
|
||||||
|
double_conso_obj.click(function() {
|
||||||
|
$("#consos_list_div").show();
|
||||||
|
$("#infos_div").attr('class', 'col-sm-5 col-xl-6');
|
||||||
|
$("#note_infos_div").attr('class', 'col-xl-3');
|
||||||
|
$("#user_select_div").attr('class', 'col-xl-4');
|
||||||
|
$("#buttons_div").attr('class', 'col-sm-7 col-xl-6');
|
||||||
|
|
||||||
|
let note_list_obj = $("#note_list");
|
||||||
|
if (buttons.length > 0 && note_list_obj.text().length > 0) {
|
||||||
|
$("#consos_list").html(note_list_obj.html());
|
||||||
|
note_list_obj.html("");
|
||||||
|
|
||||||
|
buttons.forEach(function(button) {
|
||||||
|
$("#conso_button_" + button.id).click(removeNote(button, "conso_button", buttons,
|
||||||
|
"consos_list"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let single_conso_obj = $("#single_conso");
|
||||||
|
single_conso_obj.click(function() {
|
||||||
|
$("#consos_list_div").hide();
|
||||||
|
$("#infos_div").attr('class', 'col-sm-5 col-md-4');
|
||||||
|
$("#note_infos_div").attr('class', 'col-xl-5');
|
||||||
|
$("#user_select_div").attr('class', 'col-xl-7');
|
||||||
|
$("#buttons_div").attr('class', 'col-sm-7 col-md-8');
|
||||||
|
|
||||||
|
let consos_list_obj = $("#consos_list");
|
||||||
|
if (buttons.length > 0) {
|
||||||
|
if (notes_display.length === 0 && consos_list_obj.text().length > 0) {
|
||||||
|
$("#note_list").html(consos_list_obj.html());
|
||||||
|
consos_list_obj.html("");
|
||||||
|
buttons.forEach(function(button) {
|
||||||
|
$("#conso_button_" + button.id).click(removeNote(button, "conso_button", buttons,
|
||||||
|
"note_list"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buttons.length = 0;
|
||||||
|
consos_list_obj.html("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure we begin in single consumption. Removing these lines may cause problems when reloading.
|
||||||
|
single_conso_obj.prop('checked', 'true');
|
||||||
|
double_conso_obj.removeAttr('checked');
|
||||||
|
$("label[for='double_conso']").attr('class', 'btn btn-sm btn-outline-primary');
|
||||||
|
|
||||||
|
$("#consos_list_div").hide();
|
||||||
|
|
||||||
|
$("#consume_all").click(consumeAll);
|
||||||
|
});
|
||||||
|
|
||||||
|
notes = [];
|
||||||
|
notes_display = [];
|
||||||
|
buttons = [];
|
||||||
|
|
||||||
|
// When the user searches an alias, we update the auto-completion
|
||||||
|
autoCompleteNote("note", "alias_matched", "note_list", notes, notes_display,
|
||||||
|
"alias", "note", "user_note", "profile_pic", function() {
|
||||||
|
if (buttons.length > 0 && $("#single_conso").is(":checked")) {
|
||||||
|
consumeAll();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a transaction from a button.
|
||||||
|
* @param dest Where the money goes
|
||||||
|
* @param amount The price of the item
|
||||||
|
* @param type The type of the transaction (content type id for TemplateTransaction)
|
||||||
|
* @param category_id The category identifier
|
||||||
|
* @param category_name The category name
|
||||||
|
* @param template_id The identifier of the button
|
||||||
|
* @param template_name The name of the button
|
||||||
|
*/
|
||||||
|
function addConso(dest, amount, type, category_id, category_name, template_id, template_name) {
|
||||||
|
var button = null;
|
||||||
|
buttons.forEach(function(b) {
|
||||||
|
if (b.id === template_id) {
|
||||||
|
b.quantity += 1;
|
||||||
|
button = b;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (button == null) {
|
||||||
|
button = {
|
||||||
|
id: template_id,
|
||||||
|
name: template_name,
|
||||||
|
dest: dest,
|
||||||
|
quantity: 1,
|
||||||
|
amount: amount,
|
||||||
|
type: type,
|
||||||
|
category_id: category_id,
|
||||||
|
category_name: category_name
|
||||||
|
};
|
||||||
|
buttons.push(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dc_obj = $("#double_conso");
|
||||||
|
if (dc_obj.is(":checked") || notes_display.length === 0) {
|
||||||
|
let list = dc_obj.is(":checked") ? "consos_list" : "note_list";
|
||||||
|
let html = "";
|
||||||
|
buttons.forEach(function(button) {
|
||||||
|
html += li("conso_button_" + button.id, button.name
|
||||||
|
+ "<span class=\"badge badge-dark badge-pill\">" + button.quantity + "</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#" + list).html(html);
|
||||||
|
|
||||||
|
buttons.forEach(function(button) {
|
||||||
|
$("#conso_button_" + button.id).click(removeNote(button, "conso_button", buttons, list));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
consumeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the page as its initial state.
|
||||||
|
*/
|
||||||
|
function reset() {
|
||||||
|
notes_display.length = 0;
|
||||||
|
notes.length = 0;
|
||||||
|
buttons.length = 0;
|
||||||
|
$("#note_list").html("");
|
||||||
|
$("#alias_matched").html("");
|
||||||
|
$("#consos_list").html("");
|
||||||
|
displayNote(null, "");
|
||||||
|
refreshHistory();
|
||||||
|
refreshBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply all transactions: all notes in `notes` buy each item in `buttons`
|
||||||
|
*/
|
||||||
|
function consumeAll() {
|
||||||
|
notes_display.forEach(function(note_display) {
|
||||||
|
buttons.forEach(function(button) {
|
||||||
|
consume(note_display.id, button.dest, button.quantity * note_display.quantity, button.amount,
|
||||||
|
button.name + " (" + button.category_name + ")", button.type, button.category_id, button.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new transaction from a button through the API.
|
||||||
|
* @param source The note that paid the item (type: int)
|
||||||
|
* @param dest The note that sold the item (type: int)
|
||||||
|
* @param quantity The quantity sold (type: int)
|
||||||
|
* @param amount The price of one item, in cents (type: int)
|
||||||
|
* @param reason The transaction details (type: str)
|
||||||
|
* @param type The type of the transaction (content type id for TemplateTransaction)
|
||||||
|
* @param category The category id of the button (type: int)
|
||||||
|
* @param template The button id (type: int)
|
||||||
|
*/
|
||||||
|
function consume(source, dest, quantity, amount, reason, type, category, template) {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": quantity,
|
||||||
|
"amount": amount,
|
||||||
|
"reason": reason,
|
||||||
|
"valid": true,
|
||||||
|
"polymorphic_ctype": type,
|
||||||
|
"resourcetype": "TemplateTransaction",
|
||||||
|
"source": source,
|
||||||
|
"destination": dest,
|
||||||
|
"category": category,
|
||||||
|
"template": template
|
||||||
|
}, reset).fail(function (e) {
|
||||||
|
reset();
|
||||||
|
|
||||||
|
addMsg("Une erreur est survenue lors de la transaction : " + e.responseText, "danger");
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
sources = [];
|
||||||
|
sources_notes_display = [];
|
||||||
|
dests = [];
|
||||||
|
dests_notes_display = [];
|
||||||
|
|
||||||
|
function refreshHistory() {
|
||||||
|
$("#history").load("/note/transfer/ #history");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
sources_notes_display.length = 0;
|
||||||
|
sources.length = 0;
|
||||||
|
dests_notes_display.length = 0;
|
||||||
|
dests.length = 0;
|
||||||
|
$("#source_note_list").html("");
|
||||||
|
$("#dest_note_list").html("");
|
||||||
|
$("#source_alias_matched").html("");
|
||||||
|
$("#dest_alias_matched").html("");
|
||||||
|
$("#amount").val("");
|
||||||
|
$("#reason").val("");
|
||||||
|
$("#last_name").val("");
|
||||||
|
$("#first_name").val("");
|
||||||
|
$("#bank").val("");
|
||||||
|
refreshBalance();
|
||||||
|
refreshHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
autoCompleteNote("source_note", "source_alias_matched", "source_note_list", sources, sources_notes_display,
|
||||||
|
"source_alias", "source_note", "user_note", "profile_pic");
|
||||||
|
autoCompleteNote("dest_note", "dest_alias_matched", "dest_note_list", dests, dests_notes_display,
|
||||||
|
"dest_alias", "dest_note", "user_note", "profile_pic", function() {
|
||||||
|
let last = dests_notes_display[dests_notes_display.length - 1];
|
||||||
|
dests_notes_display.length = 0;
|
||||||
|
dests_notes_display.push(last);
|
||||||
|
|
||||||
|
last.quantity = 1;
|
||||||
|
|
||||||
|
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
||||||
|
$("#last_name").val(user.last_name);
|
||||||
|
$("#first_name").val(user.first_name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure we begin in gift mode. Removing these lines may cause problems when reloading.
|
||||||
|
$("#type_gift").prop('checked', 'true');
|
||||||
|
$("#type_transfer").removeAttr('checked');
|
||||||
|
$("#type_credit").removeAttr('checked');
|
||||||
|
$("#type_debit").removeAttr('checked');
|
||||||
|
$("label[for='type_transfer']").attr('class', 'btn btn-sm btn-outline-primary');
|
||||||
|
$("label[for='type_credit']").attr('class', 'btn btn-sm btn-outline-primary');
|
||||||
|
$("label[for='type_debit']").attr('class', 'btn btn-sm btn-outline-primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#transfer").click(function() {
|
||||||
|
if ($("#type_gift").is(':checked')) {
|
||||||
|
dests_notes_display.forEach(function (dest) {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": dest.quantity,
|
||||||
|
"amount": 100 * $("#amount").val(),
|
||||||
|
"reason": $("#reason").val(),
|
||||||
|
"valid": true,
|
||||||
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
"resourcetype": "Transaction",
|
||||||
|
"source": user_id,
|
||||||
|
"destination": dest.id
|
||||||
|
}, function () {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(dest.quantity * 100 * $("#amount").val()) + " de votre note "
|
||||||
|
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}).fail(function (err) {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(dest.quantity * 100 * $("#amount").val()) + " de votre note "
|
||||||
|
+ " vers la note " + dest.name + " a échoué : " + err.responseText, "danger");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if ($("#type_transfer").is(':checked')) {
|
||||||
|
sources_notes_display.forEach(function (source) {
|
||||||
|
dests_notes_display.forEach(function (dest) {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": source.quantity * dest.quantity,
|
||||||
|
"amount": 100 * $("#amount").val(),
|
||||||
|
"reason": $("#reason").val(),
|
||||||
|
"valid": true,
|
||||||
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
"resourcetype": "Transaction",
|
||||||
|
"source": source.id,
|
||||||
|
"destination": dest.id
|
||||||
|
}, function () {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(source.quantity * dest.quantity * 100 * $("#amount").val()) + " de la note " + source.name
|
||||||
|
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}).fail(function (err) {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(source.quantity * dest.quantity * 100 * $("#amount").val()) + " de la note " + source.name
|
||||||
|
+ " vers la note " + dest.name + " a échoué : " + err.responseText, "danger");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
||||||
|
let special_note = $("#credit_type").val();
|
||||||
|
let user_note = dests_notes_display[0].id;
|
||||||
|
let given_reason = $("#reason").val();
|
||||||
|
let source, dest, reason;
|
||||||
|
if ($("#type_credit").is(':checked')) {
|
||||||
|
source = special_note;
|
||||||
|
dest = user_note;
|
||||||
|
reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase();
|
||||||
|
if (given_reason.length > 0)
|
||||||
|
reason += " (" + given_reason + ")";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
source = user_note;
|
||||||
|
dest = special_note;
|
||||||
|
reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase();
|
||||||
|
if (given_reason.length > 0)
|
||||||
|
reason += " (" + given_reason + ")";
|
||||||
|
}
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": 1,
|
||||||
|
"amount": 100 * $("#amount").val(),
|
||||||
|
"reason": reason,
|
||||||
|
"valid": true,
|
||||||
|
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
"resourcetype": "SpecialTransaction",
|
||||||
|
"source": source,
|
||||||
|
"destination": dest,
|
||||||
|
"last_name": $("#last_name").val(),
|
||||||
|
"first_name": $("#first_name").val(),
|
||||||
|
"bank": $("#bank").val()
|
||||||
|
}, function () {
|
||||||
|
addMsg("Le crédit/retrait a bien été effectué !", "success");
|
||||||
|
reset();
|
||||||
|
}).fail(function (err) {
|
||||||
|
addMsg("Le crédit/transfert a échoué : " + err.responseText, "danger");
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
{% load static i18n pretty_money static %}
|
{% load static i18n pretty_money static getenv %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
@ -46,12 +46,20 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="/static/js/base.js"></script>
|
||||||
|
|
||||||
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
||||||
{% if form.media %}
|
{% if form.media %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.validate:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
{% block extracss %}{% endblock %}
|
{% block extracss %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex w-100 h-100 flex-column">
|
<body class="d-flex w-100 h-100 flex-column">
|
||||||
|
@ -67,23 +75,27 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> Consos</a>
|
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> Clubs</a>
|
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> Activités</a>
|
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> Bouton</a>
|
<a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> {% trans 'Buttons' %}</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<i class="fa fa-user"></i> {{ user.username }} ({{ user.note.balance | pretty_money }})
|
<i class="fa fa-user"></i>
|
||||||
|
<span id="user_balance">{{ user.username }} ({{ user.note.balance | pretty_money }})</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right"
|
<div class="dropdown-menu dropdown-menu-right"
|
||||||
aria-labelledby="navbarDropdownMenuLink">
|
aria-labelledby="navbarDropdownMenuLink">
|
||||||
|
@ -112,6 +124,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container-fluid my-3" style="max-width: 1600px;">
|
<div class="container-fluid my-3" style="max-width: 1600px;">
|
||||||
{% block contenttitle %}<h1>{{ title }}</h1>{% endblock %}
|
{% block contenttitle %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
<div id="messages"></div>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Default content...</p>
|
<p>Default content...</p>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -125,7 +138,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
class="form-inline">
|
class="form-inline">
|
||||||
<span class="text-muted mr-1">
|
<span class="text-muted mr-1">
|
||||||
NoteKfet2020 —
|
NoteKfet2020 —
|
||||||
<a href="mailto:tresorie.bde@lists.crans.org"
|
<a href="mailto:{{ "CONTACT_EMAIL" | getenv }}"
|
||||||
class="text-muted">Nous contacter</a> —
|
class="text-muted">Nous contacter</a> —
|
||||||
</span>
|
</span>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -155,6 +168,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
CSRF_TOKEN = "{{ csrf_token }}";
|
||||||
|
</script>
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
{% endblock extrajavascript %}
|
{% endblock extrajavascript %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
{% load i18n %}{% load static %}{% get_current_language as LANGUAGE_CODE %}<!DOCTYPE html>
|
||||||
|
<html{% if LANGUAGE_CODE %} lang="{{LANGUAGE_CODE}}"{% endif %}>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge" /><![endif]-->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{% block title %}{% trans "Central Authentication Service" %}{% endblock %}</title>
|
||||||
|
<link href="{{settings.CAS_COMPONENT_URLS.bootstrap3_css}}" rel="stylesheet">
|
||||||
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="{{settings.CAS_COMPONENT_URLS.html5shiv}}"></script>
|
||||||
|
<script src="{{settings.CAS_COMPONENT_URLS.respond}}"></script>
|
||||||
|
<![endif]-->
|
||||||
|
{% if settings.CAS_FAVICON_URL %}<link rel="shortcut icon" href="{{settings.CAS_FAVICON_URL}}" />{% endif %}
|
||||||
|
<link href="{% static "cas_server/styles.css" %}" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<div class="container">
|
||||||
|
{% if auto_submit %}<noscript>{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<h1 id="app-name">
|
||||||
|
{% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}" alt="cas-logo" />{% endif %}
|
||||||
|
Authentification Note Kfet 2020</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if auto_submit %}</noscript>{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-12"></div>
|
||||||
|
<div class="col-lg-6 col-md-6 col-sm-8 col-xs-12">
|
||||||
|
{% if auto_submit %}<noscript>{% endif %}
|
||||||
|
{% for msg in CAS_INFO_RENDER %}
|
||||||
|
<div class="alert alert-{{msg.type}}{% if msg.discardable %} alert-dismissable{% endif %}">
|
||||||
|
{% if msg.discardable %}<button type="button" class="close" data-dismiss="alert" aria-hidden="true" id="info-{{msg.name}}">×</button>{% endif %}
|
||||||
|
<p>{{msg.message}}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
|
||||||
|
<div class="alert alert-info alert-dismissable">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-hidden="true" id="alert-version">×</button>
|
||||||
|
<p>{% blocktrans %}A new version of the application is available. This instance runs {{VERSION}} and the last version is {{LAST_VERSION}}. Please consider upgrading.{% endblocktrans %}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% block ante_messages %}{% endblock %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% spaceless %}
|
||||||
|
{% if message.level == message_levels.DEBUG %}
|
||||||
|
class="alert alert-warning"
|
||||||
|
{% elif message.level == message_levels.INFO %}
|
||||||
|
class="alert alert-info"
|
||||||
|
{% elif message.level == message_levels.SUCCESS %}
|
||||||
|
class="alert alert-success"
|
||||||
|
{% elif message.level == message_levels.WARNING %}
|
||||||
|
class="alert alert-warning"
|
||||||
|
{% else %}
|
||||||
|
class="alert alert-danger"
|
||||||
|
{% endif %}
|
||||||
|
{% endspaceless %}>
|
||||||
|
<p>{{message}}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if auto_submit %}</noscript>{% endif %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /container -->
|
||||||
|
</div>
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
{% if settings.CAS_SHOW_POWERED %}
|
||||||
|
<div id="footer">
|
||||||
|
<p><a class="text-muted" href="https://pypi.org/project/django-cas-server/">django-cas-server powered</a></p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script>
|
||||||
|
<script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script>
|
||||||
|
<script src="{% static "cas_server/functions.js" %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
|
||||||
|
discard_and_remember("#alert-version", "cas-alert-version", "{{LAST_VERSION}}");
|
||||||
|
{% endif %}
|
||||||
|
{% for msg in CAS_INFO_RENDER %}
|
||||||
|
{% if msg.discardable %}
|
||||||
|
discard_and_remember("#info-{{msg.name}}", "cas-info-{{msg.name}}", "{{msg.hash}}");
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% block javascript_inline %}{% endblock %}
|
||||||
|
</script>
|
||||||
|
{% block javascript %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!--
|
||||||
|
Powered by django-cas-server version {{VERSION}}
|
||||||
|
|
||||||
|
Pypi: https://pypi.org/project/django-cas-server/
|
||||||
|
github: https://github.com/nitmir/django-cas-server
|
||||||
|
-->
|
|
@ -0,0 +1,26 @@
|
||||||
|
{% load cas_server %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger alert-dismissable">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||||
|
{{error}}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% for field in form %}{% if not field|is_hidden %}
|
||||||
|
<div class="form-group
|
||||||
|
{% if not form.non_field_errors %}
|
||||||
|
{% if field.errors %} has-error
|
||||||
|
{% elif form.cleaned_data %} has-success
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
|
>{% spaceless %}
|
||||||
|
{% if field|is_checkbox %}
|
||||||
|
<div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label></div>
|
||||||
|
{% else %}
|
||||||
|
<label class="control-label" for="{{field.auto_id}}">{{field.label}}</label>
|
||||||
|
{{field}}
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<span class="help-block">{{error}}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endspaceless %}</div>
|
||||||
|
{% else %}{{field}}{% endif %}{% endfor %}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-success" role="alert">{% blocktrans %}<h3>Log In Successful</h3>You have successfully logged into the Central Authentication Service.<br/>For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication!{% endblocktrans %}</div>
|
||||||
|
<form class="form-signin" method="get" action="logout">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="all" value="1">{% trans "Log me out from all my sessions" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %}
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="forget_provider" value="1">{% trans "Forget the identity provider" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-danger btn-block btn-lg" type="submit">{% trans "Logout" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block ante_messages %}
|
||||||
|
{% if auto_submit %}<noscript>{% endif %}
|
||||||
|
<h2 class="form-signin-heading">{% trans "Please log in" %}</h2>
|
||||||
|
{% if auto_submit %}</noscript>{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "If you don't have any Note Kfet account, please follow <a href='/accounts/signup'>this link to sign up</a>." %}
|
||||||
|
</div>
|
||||||
|
<form class="form-signin" method="post" id="login_form"{% if post_url %} action="{{post_url}}"{% endif %}>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include "cas_server/form.html" %}
|
||||||
|
{% if auto_submit %}<noscript>{% endif %}
|
||||||
|
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Login" %}</button>
|
||||||
|
{% if auto_submit %}</noscript>{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block javascript_inline %}
|
||||||
|
jQuery(function( $ ){
|
||||||
|
$("#id_warn").click(function(e){
|
||||||
|
if($("#id_warn").is(':checked')){
|
||||||
|
createCookie("warn", "on", 10 * 365);
|
||||||
|
} else {
|
||||||
|
eraseCookie("warn");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});{% if auto_submit %}
|
||||||
|
document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-success" role="alert">{{logout_msg}}</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:proxySuccess>
|
||||||
|
<cas:proxyTicket>{{ticket}}</cas:proxyTicket>
|
||||||
|
</cas:proxySuccess>
|
||||||
|
</cas:serviceResponse>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<SOAP-ENV:Header />
|
||||||
|
<SOAP-ENV:Body>
|
||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
|
||||||
|
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"
|
||||||
|
IssueInstant="{{ IssueInstant }}"
|
||||||
|
MajorVersion="1" MinorVersion="1" Recipient="{{ Recipient }}"
|
||||||
|
ResponseID="{{ ResponseID }}">
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="samlp:Success">
|
||||||
|
</StatusCode>
|
||||||
|
</Status>
|
||||||
|
<Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" AssertionID="{{ResponseID}}"
|
||||||
|
IssueInstant="{{IssueInstant}}" Issuer="localhost" MajorVersion="1"
|
||||||
|
MinorVersion="1">
|
||||||
|
<Conditions NotBefore="{{IssueInstant}}" NotOnOrAfter="{{expireInstant}}">
|
||||||
|
<AudienceRestrictionCondition>
|
||||||
|
<Audience>
|
||||||
|
{{Recipient}}
|
||||||
|
</Audience>
|
||||||
|
</AudienceRestrictionCondition>
|
||||||
|
</Conditions>
|
||||||
|
<AttributeStatement>
|
||||||
|
<Subject>
|
||||||
|
<NameIdentifier>{{username}}</NameIdentifier>
|
||||||
|
<SubjectConfirmation>
|
||||||
|
<ConfirmationMethod>
|
||||||
|
urn:oasis:names:tc:SAML:1.0:cm:artifact
|
||||||
|
</ConfirmationMethod>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
<Attribute AttributeName="authenticationDate" AttributeNamespace="http://www.ja-sig.org/products/cas/">
|
||||||
|
<AttributeValue>{{auth_date}}</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute AttributeName="longTermAuthenticationRequestTokenUsed" AttributeNamespace="http://www.ja-sig.org/products/cas/">
|
||||||
|
<AttributeValue>false</AttributeValue>{# we do not support long-term (Remember-Me) auth #}
|
||||||
|
</Attribute>
|
||||||
|
<Attribute AttributeName="isFromNewLogin" AttributeNamespace="http://www.ja-sig.org/products/cas/">
|
||||||
|
<AttributeValue>{{is_new_login}}</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
{% for name, value in attributes %} <Attribute AttributeName="{{name}}" AttributeNamespace="http://www.ja-sig.org/products/cas/">
|
||||||
|
<AttributeValue>{{value}}</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
{% endfor %} </AttributeStatement>
|
||||||
|
<AuthenticationStatement AuthenticationInstant="{{IssueInstant}}"
|
||||||
|
AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password">
|
||||||
|
<Subject>
|
||||||
|
<NameIdentifier>{{username}}</NameIdentifier>
|
||||||
|
<SubjectConfirmation>
|
||||||
|
<ConfirmationMethod>
|
||||||
|
urn:oasis:names:tc:SAML:1.0:cm:artifact
|
||||||
|
</ConfirmationMethod>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
</AuthenticationStatement>
|
||||||
|
</Assertion>
|
||||||
|
</Response>
|
||||||
|
</SOAP-ENV:Body>
|
||||||
|
</SOAP-ENV:Envelope>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<SOAP-ENV:Header />
|
||||||
|
<SOAP-ENV:Body>
|
||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
|
||||||
|
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"
|
||||||
|
IssueInstant="{{ IssueInstant }}"
|
||||||
|
MajorVersion="1" MinorVersion="1" Recipient="{{ Recipient }}"
|
||||||
|
ResponseID="{{ ResponseID }}">
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="samlp:{{code}}">{{msg}}</StatusCode>
|
||||||
|
</Status>
|
||||||
|
</Response>
|
||||||
|
</SOAP-ENV:Body>
|
||||||
|
</SOAP-ENV:Envelope>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:authenticationSuccess>
|
||||||
|
<cas:user>{{username}}</cas:user>
|
||||||
|
<cas:attributes>
|
||||||
|
<cas:authenticationDate>{{auth_date}}</cas:authenticationDate>
|
||||||
|
<cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>{# we do not support long-term (Remember-Me) auth #}
|
||||||
|
<cas:isFromNewLogin>{{is_new_login}}</cas:isFromNewLogin>
|
||||||
|
{% for key, value in attributes %} <cas:{{key}}>{{value}}</cas:{{key}}>
|
||||||
|
{% endfor %} </cas:attributes>
|
||||||
|
<cas:attribute name="authenticationDate" value="{{auth_date}}"/>
|
||||||
|
<cas:attribute name="longTermAuthenticationRequestTokenUsed" value="false"/>
|
||||||
|
<cas:attribute name="isFromNewLogin" value="{{is_new_login}}"/>
|
||||||
|
{% for key, value in attributes %} <cas:attribute name="{{key}}" value="{{value}}"/>
|
||||||
|
{% endfor %}{% if proxyGrantingTicket %} <cas:proxyGrantingTicket>{{proxyGrantingTicket}}</cas:proxyGrantingTicket>
|
||||||
|
{% endif %}{% if proxies %} <cas:proxies>
|
||||||
|
{% for proxy in proxies %} <cas:proxy>{{proxy}}</cas:proxy>
|
||||||
|
{% endfor %} </cas:proxies>
|
||||||
|
{% endif %} </cas:authenticationSuccess>
|
||||||
|
</cas:serviceResponse>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:authenticationFailure code="{{code}}">{{msg}}</cas:authenticationFailure>
|
||||||
|
</cas:serviceResponse>
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="form-signin" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include "cas_server/form.html" %}
|
||||||
|
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Connect to the service" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<h2>{% trans "Field filters" %}</h2>
|
||||||
|
{% crispy filter.form %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% load i18n %}
|
||||||
|
<h2>{% trans "Field filters" %}</h2>
|
||||||
|
<form class="form" action="" method="get">
|
||||||
|
{{ filter.form.as_p }}
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
|
||||||
|
</form>
|
|
@ -0,0 +1 @@
|
||||||
|
{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}
|
|
@ -1,11 +1,12 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">Template Listing</a></p>
|
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Clubs list" %}</a></p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form|crispy}}
|
{{form|crispy}}
|
||||||
<button class="btn btn-primary" type="submit">Submit</button>
|
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
|
|
||||||
<a class="btn btn-primary" href="{% url 'member:club_create' %}">New Club</a>
|
<a class="btn btn-primary" href="{% url 'member:club_create' %}">{% trans "New club" %}</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body" id="profile_infos">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
|
<dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
|
||||||
|
@ -76,7 +76,9 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="historyListCollapse" class="collapse" style="overflow:auto hidden" aria-labelledby="historyListHeading" data-parent="#accordionProfile">
|
<div id="historyListCollapse" class="collapse" style="overflow:auto hidden" aria-labelledby="historyListHeading" data-parent="#accordionProfile">
|
||||||
{% render_table history_list %}
|
<div id="history_list">
|
||||||
|
{% render_table history_list %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,3 +86,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
function refreshHistory() {
|
||||||
|
$("#history_list").load("{% url 'member:user_detail' pk=object.pk %} #history_list");
|
||||||
|
$("#profile_infos").load("{% url 'member:user_detail' pk=object.pk %} #profile_infos");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}Sign Up{% endblock %}
|
{% block title %}{% trans "Sign up" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Sign up</h2>
|
<h2>{% trans "Sign up" %}</h2>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
{{ profile_form|crispy }}
|
{{ profile_form|crispy }}
|
||||||
<button class="btn btn-success" type="submit">
|
<button class="btn btn-success" type="submit">
|
||||||
{% trans "Sign Up" %}
|
{% trans "Sign up" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,97 +1,171 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% load i18n static pretty_money %}
|
{% load i18n static pretty_money django_tables2 %}
|
||||||
|
|
||||||
{# Remove page title #}
|
{# Remove page title #}
|
||||||
{% block contenttitle %}{% endblock %}
|
{% block contenttitle %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# Regroup buttons under categories #}
|
<div class="row mt-4">
|
||||||
{% regroup transaction_templates by category as categories %}
|
<div class="col-sm-5 col-md-4" id="infos_div">
|
||||||
|
<div class="row">
|
||||||
<form method="post" onsubmit="window.onbeforeunload=null">
|
{# User details column #}
|
||||||
{% csrf_token %}
|
<div class="col-xl-5" id="note_infos_div">
|
||||||
|
<div class="card border-success shadow mb-4">
|
||||||
<div class="row">
|
<img src="/media/pic/default.png"
|
||||||
<div class="col-sm-5 mb-4">
|
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block">
|
||||||
{% if form.non_field_errors %}
|
<div class="card-body text-center">
|
||||||
<p class="errornote">
|
<span id="user_note"></span>
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-row{% if field.errors %} errors{% endif %}">
|
|
||||||
{{ field.errors }}
|
|
||||||
<div>
|
|
||||||
{{ field.label_tag }}
|
|
||||||
{% if field.is_readonly %}
|
|
||||||
<div class="readonly">{{ field.contents }}</div>
|
|
||||||
{% else %}
|
|
||||||
{{ field }}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.field.help_text %}
|
|
||||||
<div class="help">{{ field.field.help_text|safe }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-7">
|
{# User selection column #}
|
||||||
<div class="card text-center shadow">
|
<div class="col-xl-7" id="user_select_div">
|
||||||
{# Tabs for button categories #}
|
<div class="card border-success shadow mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs nav-fill card-header-tabs">
|
<p class="card-text font-weight-bold">
|
||||||
{% for category in categories %}
|
{% trans "Select emitters" %}
|
||||||
<li class="nav-item">
|
</p>
|
||||||
<a class="nav-link" data-toggle="tab" href="#{{ category.grouper|slugify }}">
|
</div>
|
||||||
{{ category.grouper }}
|
<ul class="list-group list-group-flush" id="note_list">
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<div class="card-body">
|
||||||
|
<input class="form-control mx-auto d-block" type="text" id="note" />
|
||||||
{# Tabs content #}
|
<ul class="list-group list-group-flush" id="alias_matched">
|
||||||
<div class="card-body">
|
</ul>
|
||||||
<div class="tab-content">
|
|
||||||
{% for category in categories %}
|
|
||||||
<div class="tab-pane" id="{{ category.grouper|slugify }}">
|
|
||||||
<div class="d-inline-flex flex-wrap justify-content-center">
|
|
||||||
{% for button in category.list %}
|
|
||||||
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
|
||||||
name="button" value="{{ button.name }}">
|
|
||||||
{{ button.name }} ({{ button.amount | pretty_money }})
|
|
||||||
</button>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-5" id="consos_list_div">
|
||||||
|
<div class="card border-info shadow mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "Select consumptions" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" id="consos_list">
|
||||||
|
</ul>
|
||||||
|
<button id="consume_all" class="form-control btn btn-primary">
|
||||||
|
{% trans "Consume!" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
|
{# Buttons column #}
|
||||||
|
<div class="col-sm-7 col-md-8" id="buttons_div">
|
||||||
|
{# Show last used buttons #}
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "Most used buttons" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-nowrap" style="overflow:auto hidden">
|
||||||
|
<div class="d-inline-flex flex-wrap justify-content-center" id="most_used">
|
||||||
|
{% for button in most_used %}
|
||||||
|
{% if button.display %}
|
||||||
|
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
||||||
|
id="most_used_button{{ button.id }}" name="button" value="{{ button.name }}">
|
||||||
|
{{ button.name }} ({{ button.amount | pretty_money }})
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Regroup buttons under categories #}
|
||||||
|
{% regroup transaction_templates by category as categories %}
|
||||||
|
|
||||||
|
<div class="card border-primary text-center shadow mb-4">
|
||||||
|
{# Tabs for button categories #}
|
||||||
|
<div class="card-header">
|
||||||
|
<ul class="nav nav-tabs nav-fill card-header-tabs">
|
||||||
|
{% for category in categories %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link font-weight-bold" data-toggle="tab" href="#{{ category.grouper|slugify }}">
|
||||||
|
{{ category.grouper }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Tabs content #}
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tab-content">
|
||||||
|
{% for category in categories %}
|
||||||
|
<div class="tab-pane" id="{{ category.grouper|slugify }}">
|
||||||
|
<div class="d-inline-flex flex-wrap justify-content-center">
|
||||||
|
{% for button in category.list %}
|
||||||
|
{% if button.display %}
|
||||||
|
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
||||||
|
id="button{{ button.id }}" name="button" value="{{ button.name }}">
|
||||||
|
{{ button.name }} ({{ button.amount | pretty_money }})
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Mode switch #}
|
||||||
|
<div class="card-footer border-primary">
|
||||||
|
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
||||||
|
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||||
|
</a>
|
||||||
|
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
||||||
|
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
||||||
|
<input type="radio" name="conso_type" id="single_conso" checked>
|
||||||
|
{% trans "Single consumptions" %}
|
||||||
|
</label>
|
||||||
|
<label for="double_conso" class="btn btn-sm btn-outline-primary">
|
||||||
|
<input type="radio" name="conso_type" id="double_conso">
|
||||||
|
{% trans "Double consumptions" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4" id="history">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "Recent transactions history" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% render_table table %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
|
<script type="text/javascript" src="/static/js/consos.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
{% for button in most_used %}
|
||||||
// If hash of a category in the URL, then select this category
|
{% if button.display %}
|
||||||
// else select the first one
|
$("#most_used_button{{ button.id }}").click(function() {
|
||||||
if (location.hash) {
|
addConso({{ button.destination.id }}, {{ button.amount }},
|
||||||
$("a[href='" + location.hash + "']").tab("show");
|
{{ polymorphic_ctype }}, {{ button.category.id }}, "{{ button.category.name }}",
|
||||||
} else {
|
{{ button.id }}, "{{ button.name }}");
|
||||||
$("a[data-toggle='tab']").first().tab("show");
|
});
|
||||||
}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
// When selecting a category, change URL
|
{% for button in transaction_templates %}
|
||||||
$(document.body).on("click", "a[data-toggle='tab']", function(event) {
|
{% if button.display %}
|
||||||
location.hash = this.getAttribute("href");
|
$("#button{{ button.id }}").click(function() {
|
||||||
});
|
addConso({{ button.destination.id }}, {{ button.amount }},
|
||||||
});
|
{{ polymorphic_ctype }}, {{ button.category.id }}, "{{ button.category.name }}",
|
||||||
|
{{ button.id }}, "{{ button.name }}");
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,35 +3,188 @@
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% load i18n static %}
|
{% load i18n static django_tables2 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post" onsubmit="window.onbeforeunload=null">{% csrf_token %}
|
|
||||||
{% if form.non_field_errors %}
|
<div class="row">
|
||||||
<p class="errornote">
|
<div class="col-xl-12">
|
||||||
{% for error in form.non_field_errors %}
|
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
|
||||||
{{ error }}
|
<label for="type_gift" class="btn btn-sm btn-outline-primary active">
|
||||||
{% endfor %}
|
<input type="radio" name="transaction_type" id="type_gift" checked>
|
||||||
</p>
|
{% trans "Gift" %}
|
||||||
{% endif %}
|
</label>
|
||||||
<fieldset class="module aligned">
|
<label for="type_transfer" class="btn btn-sm btn-outline-primary">
|
||||||
{% for field in form %}
|
<input type="radio" name="transaction_type" id="type_transfer">
|
||||||
<div class="form-row{% if field.errors %} errors{% endif %}">
|
{% trans "Transfer" %}
|
||||||
{{ field.errors }}
|
</label>
|
||||||
<div>
|
<label for="type_credit" class="btn btn-sm btn-outline-primary">
|
||||||
{{ field.label_tag }}
|
<input type="radio" name="transaction_type" id="type_credit">
|
||||||
{% if field.is_readonly %}
|
{% trans "Credit" %}
|
||||||
<div class="readonly">{{ field.contents }}</div>
|
</label>
|
||||||
{% else %}
|
<label type="type_debit" class="btn btn-sm btn-outline-primary">
|
||||||
{{ field }}
|
<input type="radio" name="transaction_type" id="type_debit">
|
||||||
{% endif %}
|
{% trans "Debit" %}
|
||||||
{% if field.field.help_text %}
|
</label>
|
||||||
<div class="help">{{ field.field.help_text|safe }}</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4" id="emitters_div" style="display: none;">
|
||||||
|
<div class="card border-success shadow mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "Select emitters" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" id="source_note_list">
|
||||||
|
</ul>
|
||||||
|
<div class="card-body">
|
||||||
|
<input class="form-control mx-auto d-block" type="text" id="source_note" />
|
||||||
|
<ul class="list-group list-group-flush" id="source_alias_matched">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-4" id="note_infos_div">
|
||||||
|
<div class="card border-success shadow mb-4">
|
||||||
|
<img src="/media/pic/default.png"
|
||||||
|
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<span id="user_note"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4" id="external_div" style="display: none;">
|
||||||
|
<div class="card border-success shadow mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "External payment" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" id="source_note_list">
|
||||||
|
</ul>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="credit_type">{% trans "Transfer type" %} :</label>
|
||||||
|
<select id="credit_type" class="custom-select">
|
||||||
|
{% for special_type in special_types %}
|
||||||
|
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="last_name">{% trans "Name" %} :</label>
|
||||||
|
<input type="text" id="last_name" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="first_name">{% trans "First name" %} :</label>
|
||||||
|
<input type="text" id="first_name" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="bank">{% trans "Bank" %} :</label>
|
||||||
|
<input type="text" id="bank" class="form-control" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
<input type="submit" value="{% trans 'Transfer' %}">
|
|
||||||
</form>
|
<div class="col-md-8" id="dests_div">
|
||||||
|
<div class="card border-info shadow mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold" id="dest_title">
|
||||||
|
{% trans "Select receivers" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" id="dest_note_list">
|
||||||
|
</ul>
|
||||||
|
<div class="card-body">
|
||||||
|
<input class="form-control mx-auto d-block" type="text" id="dest_note" />
|
||||||
|
<ul class="list-group list-group-flush" id="dest_alias_matched">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="amount">{% trans "Amount" %} :</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control mx-auto d-block" type="number" min="0" step="0.01" id="amount" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">€</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="reason">{% trans "Reason" %} :</label>
|
||||||
|
<input class="form-control mx-auto d-block" type="text" id="reason" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button id="transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4" id="history">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-text font-weight-bold">
|
||||||
|
{% trans "Recent transactions history" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% render_table table %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }};
|
||||||
|
SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }};
|
||||||
|
user_id = {{ user.note.pk }};
|
||||||
|
|
||||||
|
$("#type_gift").click(function() {
|
||||||
|
$("#emitters_div").hide();
|
||||||
|
$("#external_div").hide();
|
||||||
|
$("#dests_div").attr('class', 'col-md-8');
|
||||||
|
$("#dest_title").text("{% trans "Select receivers" %}");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#type_transfer").click(function() {
|
||||||
|
$("#emitters_div").show();
|
||||||
|
$("#external_div").hide();
|
||||||
|
$("#dests_div").attr('class', 'col-md-4');
|
||||||
|
$("#dest_title").text("{% trans "Select receivers" %}");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#type_credit").click(function() {
|
||||||
|
$("#emitters_div").hide();
|
||||||
|
$("#external_div").show();
|
||||||
|
$("#dests_div").attr('class', 'col-md-4');
|
||||||
|
$("#dest_title").text("{% trans "Credit note" %}");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#type_debit").click(function() {
|
||||||
|
$("#emitters_div").hide();
|
||||||
|
$("#external_div").show();
|
||||||
|
$("#dests_div").attr('class', 'col-md-4');
|
||||||
|
$("#dest_title").text("{% trans "Debit note" %}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="/static/js/transfer.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">Template Listing</a></p>
|
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Buttons list" %}</a></p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form|crispy}}
|
{{form|crispy}}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<td><a href="{{object.get_absolute_url}}">{{ object.name }}</a></td>
|
<td><a href="{{object.get_absolute_url}}">{{ object.name }}</a></td>
|
||||||
<td>{{ object.destination }}</td>
|
<td>{{ object.destination }}</td>
|
||||||
<td>{{ object.amount | pretty_money }}</td>
|
<td>{{ object.amount | pretty_money }}</td>
|
||||||
<td>{{ object.template_type }}</td>
|
<td>{{ object.category }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -17,6 +17,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Vous pouvez aussi vous connecter via l'authentification centralisée <a href="{% url 'cas_login' %}">en suivant ce lien.</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
|
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
|
||||||
{{ form | crispy }}
|
{{ form | crispy }}
|
||||||
<input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary">
|
<input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary">
|
||||||
|
|
12
tox.ini
12
tox.ini
|
@ -9,7 +9,10 @@ skipsdist = True
|
||||||
setenv =
|
setenv =
|
||||||
PYTHONWARNINGS = all
|
PYTHONWARNINGS = all
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements/base.txt
|
||||||
|
-r{toxinidir}/requirements/api.txt
|
||||||
|
-r{toxinidir}/requirements/cas.txt
|
||||||
|
-r{toxinidir}/requirements/production.txt
|
||||||
coverage
|
coverage
|
||||||
commands =
|
commands =
|
||||||
./manage.py makemigrations
|
./manage.py makemigrations
|
||||||
|
@ -18,7 +21,10 @@ commands =
|
||||||
|
|
||||||
[testenv:linters]
|
[testenv:linters]
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements/base.txt
|
||||||
|
-r{toxinidir}/requirements/api.txt
|
||||||
|
-r{toxinidir}/requirements/cas.txt
|
||||||
|
-r{toxinidir}/requirements/production.txt
|
||||||
flake8
|
flake8
|
||||||
flake8-colors
|
flake8-colors
|
||||||
flake8-import-order
|
flake8-import-order
|
||||||
|
@ -26,7 +32,7 @@ deps =
|
||||||
pep8-naming
|
pep8-naming
|
||||||
pyflakes
|
pyflakes
|
||||||
commands =
|
commands =
|
||||||
flake8 apps/activity apps/api apps/member apps/note
|
flake8 apps/activity apps/api apps/logs apps/member apps/note
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# Ignore too many errors, should be reduced in the future
|
# Ignore too many errors, should be reduced in the future
|
||||||
|
|
Loading…
Reference in New Issue