1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-11-30 04:13:01 +00:00

Merge branch 'fix-what-i-broke' into 'master'

Corrections

Closes #36

See merge request bde/nk20!56
This commit is contained in:
ynerant 2020-03-11 18:01:02 +01:00
commit 1f004191a8
42 changed files with 686 additions and 319 deletions

13
.env_example Normal file
View File

@ -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"

View File

@ -9,13 +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/ COPY . /code/
# Comment what is not needed
RUN pip install -r requirements/base.txt RUN pip install -r requirements/base.txt
RUN pip install -r requirements/api.txt RUN pip install -r requirements/api.txt
RUN pip install -r requirements/cas.txt RUN pip install -r requirements/cas.txt
RUN pip install -r requirements/production.txt RUN pip install -r requirements/production.txt
COPY . /code/
ENTRYPOINT ["/code/entrypoint.sh"] ENTRYPOINT ["/code/entrypoint.sh"]
EXPOSE 8000 EXPOSE 8000

View File

@ -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é serveur : 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 !**

View File

@ -1,7 +1,8 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
from ..models import ActivityType, Activity, Guest from ..models import ActivityType, Activity, Guest
@ -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', ]

View File

@ -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):
@ -24,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.
@ -32,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'

View File

View File

@ -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

11
apps/logs/api/urls.py Normal file
View File

@ -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)

23
apps/logs/api/views.py Normal file
View File

@ -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', ]

View File

@ -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)

55
apps/logs/middlewares.py Normal file
View File

@ -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

View File

@ -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'),
) )

View File

@ -1,67 +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.proxygrantingticket',
'cas_server.proxyticket',
'cas_server.serviceticket',
'cas_server.user', 'cas_server.user',
'cas_server.userattributes', 'cas_server.userattributes',
'contenttypes.contenttype', 'contenttypes.contenttype',
'logs.changelog', 'logs.changelog', # Never remove this line
'migrations.migration', 'migrations.migration',
'note.noteuser', 'note.note' # We only store the subclasses
'note.noteclub', 'note.transaction',
'note.notespecial',
'sessions.session', 'sessions.session',
'reversion.revision',
'reversion.version',
] ]
@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()
@ -69,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,
@ -105,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),

View File

@ -1,8 +0,0 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
app_name = 'logs'
# TODO User interface
urlpatterns = [
]

View File

@ -2,6 +2,7 @@
# 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 .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
from ..models import Profile, Club, Role, Membership from ..models import Profile, Club, Role, Membership
@ -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):

View File

@ -46,7 +46,7 @@ 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']) ] 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,))
@ -153,7 +153,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']) ] indexes = [models.Index(fields=['user'])]
# @receiver(post_save, sender=settings.AUTH_USER_MODEL) # @receiver(post_save, sender=settings.AUTH_USER_MODEL)
# def save_user_profile(instance, created, **_kwargs): # def save_user_profile(instance, created, **_kwargs):

View File

@ -300,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

View File

@ -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
class NoteSerializer(serializers.ModelSerializer): class NoteSerializer(serializers.ModelSerializer):
@ -78,6 +79,17 @@ 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.
@ -100,6 +112,17 @@ class TransactionSerializer(serializers.ModelSerializer):
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.
@ -109,3 +132,11 @@ class MembershipTransactionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = MembershipTransaction model = MembershipTransaction
fields = '__all__' fields = '__all__'
class TransactionPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Transaction: TransactionSerializer,
TemplateTransaction: TemplateTransactionSerializer,
MembershipTransaction: MembershipTransactionSerializer,
}

View File

@ -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)

View File

@ -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 SearchFilter
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.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
class NoteViewSet(viewsets.ModelViewSet): class NoteViewSet(viewsets.ModelViewSet):
@ -69,8 +71,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:
@ -107,7 +109,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 +133,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 +153,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 +164,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

View File

@ -3,7 +3,7 @@
"model": "note.note", "model": "note.note",
"pk": 1, "pk": 1,
"fields": { "fields": {
"polymorphic_ctype": 39, "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": 39, "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": 39, "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": 39, "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": 38, "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": 38, "polymorphic_ctype": 39,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",

View File

@ -235,7 +235,7 @@ 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:

View File

@ -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)

View File

@ -69,7 +69,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)
@ -141,7 +141,7 @@ class ConsoView(LoginRequiredMixin, SingleTableView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['transaction_templates'] = TransactionTemplate.objects.filter(display=True) \ context['transaction_templates'] = TransactionTemplate.objects.filter(display=True) \
.order_by('category') .order_by('category')
context['title'] = _("Consommations") context['title'] = _("Consumptions")
# select2 compatibility # select2 compatibility
context['no_cache'] = True context['no_cache'] = True

View File

@ -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

View File

@ -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-03-07 18:01+0100\n" "POT-Creation-Date: 2020-03-11 11:44+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"
@ -24,7 +24,7 @@ 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:60 apps/member/models.py:111
#: apps/note/models/notes.py:187 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:15 #: apps/note/models/transactions.py:44 templates/member/profile_detail.html:15
msgid "name" msgid "name"
msgstr "" msgstr ""
@ -49,7 +49,7 @@ msgstr ""
msgid "description" msgid "description"
msgstr "" msgstr ""
#: apps/activity/models.py:54 apps/note/models/notes.py:163 #: apps/activity/models.py:54 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:62 #: apps/note/models/transactions.py:62
msgid "type" msgid "type"
msgstr "" msgstr ""
@ -90,7 +90,7 @@ msgstr ""
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#: apps/logs/models.py:21 apps/note/models/notes.py:116 #: apps/logs/models.py:21 apps/note/models/notes.py:117
msgid "user" msgid "user"
msgstr "" msgstr ""
@ -114,15 +114,27 @@ msgstr ""
msgid "new data" msgid "new data"
msgstr "" msgstr ""
#: apps/logs/models.py:59 #: 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:67 #: apps/logs/models.py:73
msgid "timestamp" msgid "timestamp"
msgstr "" msgstr ""
#: apps/logs/models.py:71 #: apps/logs/models.py:77
msgid "Logs cannot be destroyed." msgid "Logs cannot be destroyed."
msgstr "" msgstr ""
@ -188,7 +200,7 @@ msgid ""
"members can renew their membership." "members can renew their membership."
msgstr "" msgstr ""
#: apps/member/models.py:93 apps/note/models/notes.py:138 #: apps/member/models.py:93 apps/note/models/notes.py:139
msgid "club" msgid "club"
msgstr "" msgstr ""
@ -237,7 +249,7 @@ msgstr ""
msgid "Account #%(id)s: %(username)s" msgid "Account #%(id)s: %(username)s"
msgstr "" msgstr ""
#: apps/member/views.py:200 #: apps/member/views.py:202
msgid "Alias successfully deleted" msgid "Alias successfully deleted"
msgstr "" msgstr ""
@ -250,127 +262,127 @@ msgstr ""
msgid "destination" msgid "destination"
msgstr "" msgstr ""
#: apps/note/apps.py:14 apps/note/models/notes.py:57 #: apps/note/apps.py:14 apps/note/models/notes.py:58
msgid "note" msgid "note"
msgstr "" msgstr ""
#: apps/note/forms.py:26 #: apps/note/forms.py:20
msgid "New Alias" msgid "New Alias"
msgstr "" msgstr ""
#: apps/note/forms.py:31 #: apps/note/forms.py:25
msgid "select an image" msgid "select an image"
msgstr "" msgstr ""
#: apps/note/forms.py:32 #: apps/note/forms.py:26
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "" msgstr ""
#: apps/note/forms.py:77 #: apps/note/forms.py:70
msgid "Source and destination must be different." msgid "Source and destination must be different."
msgstr "" msgstr ""
#: apps/note/models/notes.py:26 #: apps/note/models/notes.py:27
msgid "account balance" msgid "account balance"
msgstr "" msgstr ""
#: 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 "" 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:52 apps/note/models/transactions.py:102 #: apps/note/models/notes.py:53 apps/note/models/transactions.py:102
msgid "created at" msgid "created at"
msgstr "" msgstr ""
#: apps/note/models/notes.py:58 #: apps/note/models/notes.py:59
msgid "notes" msgid "notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:66 #: apps/note/models/notes.py:67
msgid "Note" msgid "Note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:76 apps/note/models/notes.py:100 #: 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:120 #: apps/note/models/notes.py:121
msgid "one's note" msgid "one's note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:121 #: apps/note/models/notes.py:122
msgid "users note" msgid "users note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:127 #: 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:142 #: apps/note/models/notes.py:143
msgid "club note" msgid "club note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:143 #: apps/note/models/notes.py:144
msgid "clubs notes" msgid "clubs notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:149 #: 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:169 #: apps/note/models/notes.py:170
msgid "special note" msgid "special note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:170 #: apps/note/models/notes.py:171
msgid "special notes" msgid "special notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:193 #: apps/note/models/notes.py:194
msgid "Invalid alias" msgid "Invalid alias"
msgstr "" msgstr ""
#: apps/note/models/notes.py:209 #: apps/note/models/notes.py:210
msgid "alias" msgid "alias"
msgstr "" msgstr ""
#: apps/note/models/notes.py:210 templates/member/profile_detail.html:37 #: apps/note/models/notes.py:211 templates/member/profile_detail.html:37
msgid "aliases" msgid "aliases"
msgstr "" msgstr ""
#: apps/note/models/notes.py:228 #: apps/note/models/notes.py:229
msgid "Alias is too long." msgid "Alias is too long."
msgstr "" msgstr ""
#: apps/note/models/notes.py:233 #: apps/note/models/notes.py:234
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:242 #: apps/note/models/notes.py:243
msgid "You can't delete your main alias." msgid "You can't delete your main alias."
msgstr "" msgstr ""
@ -422,11 +434,11 @@ msgstr ""
msgid "transactions" msgid "transactions"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:184 #: apps/note/models/transactions.py:185
msgid "membership transaction" msgid "membership transaction"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:185 #: apps/note/models/transactions.py:186
msgid "membership transactions" msgid "membership transactions"
msgstr "" msgstr ""
@ -434,34 +446,54 @@ msgstr ""
msgid "Transfer money from your account to one or others" msgid "Transfer money from your account to one or others"
msgstr "" msgstr ""
#: apps/note/views.py:138 #: apps/note/views.py:139
msgid "Consommations" msgid "Consommations"
msgstr "" msgstr ""
#: note_kfet/settings/base.py:162 #: note_kfet/settings/__init__.py:63
msgid "German"
msgstr ""
#: note_kfet/settings/base.py:163
msgid "English"
msgstr ""
#: note_kfet/settings/base.py:164
msgid "French"
msgstr ""
#: note_kfet/settings/base.py:215
msgid "" msgid ""
"The Central Authentication Service grants you access to most of our websites " "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 " "by authenticating only once, so you don't need to type your credentials "
"again unless your session expires or you logout." "again unless your session expires or you logout."
msgstr "" msgstr ""
#: note_kfet/settings/base.py:156
msgid "German"
msgstr ""
#: note_kfet/settings/base.py:157
msgid "English"
msgstr ""
#: note_kfet/settings/base.py:158
msgid "French"
msgstr ""
#: templates/base.html:13 #: templates/base.html:13
msgid "The ENS Paris-Saclay BDE note." msgid "The ENS Paris-Saclay BDE note."
msgstr "" msgstr ""
#: templates/cas_server/base.html:7 templates/cas_server/base.html:26 #: templates/base.html:70
msgid "Consumptions"
msgstr ""
#: templates/base.html:73
msgid "Clubs"
msgstr ""
#: templates/base.html:76
msgid "Activities"
msgstr ""
#: templates/base.html:79
msgid "Button"
msgstr ""
#: templates/base.html:82 templates/note/transaction_form.html:35
msgid "Transfer"
msgstr ""
#: templates/cas_server/base.html:7
msgid "Central Authentication Service" msgid "Central Authentication Service"
msgstr "" msgstr ""
@ -511,6 +543,15 @@ msgstr ""
msgid "Connect to the service" msgid "Connect to the service"
msgstr "" 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
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 ""
@ -583,10 +624,6 @@ msgstr ""
msgid "Sign Up" msgid "Sign Up"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:35
msgid "Transfer"
msgstr ""
#: 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."
msgstr "" msgstr ""
@ -596,7 +633,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 ""
@ -608,7 +645,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 ""

View File

@ -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-03-07 18:01+0100\n" "POT-Creation-Date: 2020-03-11 11:44+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"
@ -19,7 +19,7 @@ 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:60 apps/member/models.py:111
#: apps/note/models/notes.py:187 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:15 #: apps/note/models/transactions.py:44 templates/member/profile_detail.html:15
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
@ -44,7 +44,7 @@ msgstr "types d'activité"
msgid "description" msgid "description"
msgstr "description" msgstr "description"
#: apps/activity/models.py:54 apps/note/models/notes.py:163 #: apps/activity/models.py:54 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:62 #: apps/note/models/transactions.py:62
msgid "type" msgid "type"
msgstr "type" msgstr "type"
@ -85,15 +85,13 @@ msgstr ""
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#: apps/logs/models.py:21 apps/note/models/notes.py:116 #: 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
#, fuzzy
#| msgid "address"
msgid "IP Address" msgid "IP Address"
msgstr "adresse" msgstr "Adresse IP"
#: apps/logs/models.py:35 #: apps/logs/models.py:35
msgid "model" msgid "model"
@ -108,22 +106,30 @@ msgid "previous data"
msgstr "Données précédentes" msgstr "Données précédentes"
#: apps/logs/models.py:52 #: 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:59 #: 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:67 #: apps/logs/models.py:73
msgid "timestamp" msgid "timestamp"
msgstr "Date" msgstr "Date"
#: apps/logs/models.py:71 #: 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."
@ -193,10 +199,16 @@ 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:138 #: apps/member/models.py:93 apps/note/models/notes.py:139
msgid "club" msgid "club"
msgstr "club" msgstr "club"
msgid "New club"
msgstr "Nouveau club"
msgid "Clubs list"
msgstr "Liste des clubs"
#: apps/member/models.py:94 #: apps/member/models.py:94
msgid "clubs" msgid "clubs"
msgstr "clubs" msgstr "clubs"
@ -242,9 +254,9 @@ msgstr "Un alias avec un nom similaire existe déjà."
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/member/views.py:200 #: apps/member/views.py:202
msgid "Alias successfully deleted" msgid "Alias successfully deleted"
msgstr "" msgstr "L'alias a bien été supprimé"
#: apps/note/admin.py:120 apps/note/models/transactions.py:93 #: apps/note/admin.py:120 apps/note/models/transactions.py:93
msgid "source" msgid "source"
@ -255,132 +267,128 @@ msgstr "source"
msgid "destination" msgid "destination"
msgstr "destination" msgstr "destination"
#: apps/note/apps.py:14 apps/note/models/notes.py:57 #: apps/note/apps.py:14 apps/note/models/notes.py:58
msgid "note" msgid "note"
msgstr "note" msgstr "note"
#: apps/note/forms.py:26 #: apps/note/forms.py:20
msgid "New Alias" msgid "New Alias"
msgstr "" msgstr "Nouvel alias"
#: apps/note/forms.py:31 #: apps/note/forms.py:25
#, fuzzy
#| msgid "display image"
msgid "select an image" msgid "select an image"
msgstr "image affichée" msgstr "Choisissez une image"
#: apps/note/forms.py:32 #: apps/note/forms.py:26
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "" msgstr "Taille maximale : 2 Mo"
#: apps/note/forms.py:77 #: apps/note/forms.py:70
msgid "Source and destination must be different." msgid "Source and destination must be different."
msgstr "La source et la destination doivent être différentes." msgstr "La source et la destination doivent être différentes."
#: apps/note/models/notes.py:26 #: 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:52 apps/note/models/transactions.py:102 #: apps/note/models/notes.py:53 apps/note/models/transactions.py:102
msgid "created at" msgid "created at"
msgstr "créée le" msgstr "créée le"
#: apps/note/models/notes.py:58 #: apps/note/models/notes.py:59
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
#: apps/note/models/notes.py:66 #: apps/note/models/notes.py:67
msgid "Note" msgid "Note"
msgstr "Note" msgstr "Note"
#: apps/note/models/notes.py:76 apps/note/models/notes.py:100 #: 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:120 #: apps/note/models/notes.py:121
msgid "one's note" msgid "one's note"
msgstr "note d'un utilisateur" msgstr "note d'un utilisateur"
#: apps/note/models/notes.py:121 #: 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:127 #: 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:142 #: 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:143 #: 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:149 #: 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:169 #: 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:170 #: 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:193 #: apps/note/models/notes.py:194
msgid "Invalid alias" msgid "Invalid alias"
msgstr "Alias invalide" msgstr "Alias invalide"
#: apps/note/models/notes.py:209 #: apps/note/models/notes.py:210
msgid "alias" msgid "alias"
msgstr "alias" msgstr "alias"
#: apps/note/models/notes.py:210 templates/member/profile_detail.html:37 #: apps/note/models/notes.py:211 templates/member/profile_detail.html:37
msgid "aliases" msgid "aliases"
msgstr "alias" msgstr "alias"
#: apps/note/models/notes.py:228 #: apps/note/models/notes.py:229
msgid "Alias is too long." msgid "Alias is too long."
msgstr "L'alias est trop long." msgstr "L'alias est trop long."
#: apps/note/models/notes.py:233 #: apps/note/models/notes.py:234
#, fuzzy
#| msgid "An alias with a similar name already exists:"
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:242 #: apps/note/models/notes.py:243
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."
@ -393,7 +401,6 @@ 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à."
@ -433,11 +440,11 @@ msgstr "transaction"
msgid "transactions" msgid "transactions"
msgstr "transactions" msgstr "transactions"
#: apps/note/models/transactions.py:184 #: apps/note/models/transactions.py:185
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:186
msgid "membership transactions" msgid "membership transactions"
msgstr "transactions d'adhésion" msgstr "transactions d'adhésion"
@ -445,34 +452,53 @@ msgstr "transactions d'adhésion"
msgid "Transfer money from your account to one or others" msgid "Transfer money from your account to one or others"
msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres" msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres"
#: apps/note/views.py:138 #: note_kfet/settings/__init__.py:63
msgid "Consommations"
msgstr "transactions"
#: note_kfet/settings/base.py:162
msgid "German"
msgstr ""
#: note_kfet/settings/base.py:163
msgid "English"
msgstr ""
#: note_kfet/settings/base.py:164
msgid "French"
msgstr ""
#: note_kfet/settings/base.py:215
msgid "" msgid ""
"The Central Authentication Service grants you access to most of our websites " "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 " "by authenticating only once, so you don't need to type your credentials "
"again unless your session expires or you logout." "again unless your session expires or you logout."
msgstr "" msgstr ""
#: note_kfet/settings/base.py:156
msgid "German"
msgstr ""
#: note_kfet/settings/base.py:157
msgid "English"
msgstr ""
#: note_kfet/settings/base.py:158
msgid "French"
msgstr ""
#: templates/base.html:13 #: templates/base.html:13
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/cas_server/base.html:7 templates/cas_server/base.html:26 #: templates/base.html:70
msgid "Consumptions"
msgstr "Consommations"
#: templates/base.html:73
msgid "Clubs"
msgstr "Clubs"
#: templates/base.html:76
msgid "Activities"
msgstr "Activités"
#: templates/base.html:79
msgid "Buttons"
msgstr "Boutons"
msgid "Buttons list"
msgstr "Liste des boutons"
#: templates/base.html:82 templates/note/transaction_form.html:35
msgid "Transfer"
msgstr "Virement"
#: templates/cas_server/base.html:7
msgid "Central Authentication Service" msgid "Central Authentication Service"
msgstr "" msgstr ""
@ -510,11 +536,11 @@ msgstr ""
#: templates/cas_server/login.html:11 #: templates/cas_server/login.html:11
msgid "" msgid ""
"If you don't have any Note Kfet account, please follow <a href='/accounts" "If you don't have any Note Kfet account, please follow <a href='/accounts/"
"/signup'>this link to sign up</a>." "signup'>this link to sign up</a>."
msgstr "" msgstr ""
"Si vous n'avez pas de compte Note Kfet, veuillez suivre <a href='/accounts" "Si vous n'avez pas de compte Note Kfet, veuillez suivre <a href='/accounts/"
"/signup'>ce lien pour vous inscrire</a>." "signup'>ce lien pour vous inscrire</a>."
#: templates/cas_server/login.html:17 #: templates/cas_server/login.html:17
msgid "Login" msgid "Login"
@ -524,6 +550,15 @@ msgstr ""
msgid "Connect to the service" msgid "Connect to the service"
msgstr "" 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
msgid "Submit"
msgstr ""
#: 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"
@ -557,10 +592,8 @@ msgid "Regenerate token"
msgstr "Regénérer le jeton" msgstr "Regénérer le jeton"
#: templates/member/profile_alias.html:10 #: templates/member/profile_alias.html:10
#, fuzzy
#| msgid "alias"
msgid "Add alias" msgid "Add alias"
msgstr "alias" msgstr "Ajouter un alias"
#: templates/member/profile_detail.html:15 #: templates/member/profile_detail.html:15
msgid "first name" msgid "first name"
@ -583,10 +616,8 @@ msgid "Manage auth token"
msgstr "Gérer les jetons d'authentification" msgstr "Gérer les jetons d'authentification"
#: templates/member/profile_detail.html:49 #: templates/member/profile_detail.html:49
#, fuzzy
#| msgid "Update Profile"
msgid "View Profile" msgid "View Profile"
msgstr "Modifier le profil" msgstr "Voir le profil"
#: templates/member/profile_detail.html:62 #: templates/member/profile_detail.html:62
msgid "View my memberships" msgid "View my memberships"
@ -596,13 +627,10 @@ msgstr "Voir mes adhésions"
msgid "Save Changes" msgid "Save Changes"
msgstr "Sauvegarder les changements" msgstr "Sauvegarder les changements"
#: 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
msgid "Transfer"
msgstr "Virement"
#: 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."
@ -613,7 +641,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 ""
@ -625,7 +653,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 ""

View File

@ -1,4 +1,9 @@
# 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 *
@ -30,10 +35,6 @@ 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 = [os.environ.get('ALLOWED_HOSTS', 'localhost')]
else: else:
from .development import * from .development import *
@ -46,12 +47,14 @@ except ImportError:
if "cas" in INSTALLED_APPS: if "cas" in INSTALLED_APPS:
MIDDLEWARE += ['cas.middleware.CASMiddleware'] MIDDLEWARE += ['cas.middleware.CASMiddleware']
# CAS Settings # CAS Settings
CAS_SERVER_URL = "https://" + os.getenv("NOTE_URL", "note.example.com") + "/cas/"
CAS_AUTO_CREATE_USER = False CAS_AUTO_CREATE_USER = False
CAS_LOGO_URL = "/static/img/Saperlistpopette.png" CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
CAS_FAVICON_URL = "/static/favicon/favicon-32x32.png" CAS_FAVICON_URL = "/static/favicon/favicon-32x32.png"
CAS_SHOW_SERVICE_MESSAGES = True CAS_SHOW_SERVICE_MESSAGES = True
CAS_SHOW_POWERED = False CAS_SHOW_POWERED = False
CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False
CAS_PROVIDE_URL_TO_LOGOUT = True
CAS_INFO_MESSAGES = { CAS_INFO_MESSAGES = {
"cas_explained": { "cas_explained": {
"message": _( "message": _(
@ -68,7 +71,11 @@ if "cas" in INSTALLED_APPS:
'cas_explained', 'cas_explained',
] ]
AUTHENTICATION_BACKENDS += ('cas.backends.CASBackend',) AUTHENTICATION_BACKENDS += ('cas.backends.CASBackend',)
if "logs" in INSTALLED_APPS:
MIDDLEWARE += ('logs.middlewares.LogsMiddleware',)
if "debug_toolbar" in INSTALLED_APPS: if "debug_toolbar" in INSTALLED_APPS:
MIDDLEWARE.insert(1,"debug_toolbar.middleware.DebugToolbarMiddleware") MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = [ '127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']

View File

@ -39,6 +39,8 @@ INSTALLED_APPS = [
'polymorphic', 'polymorphic',
'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',
@ -135,11 +137,14 @@ REST_FRAMEWORK = {
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [ 'DEFAULT_PERMISSION_CLASSES': [
# TODO Maybe replace it with our custom permissions system # TODO Maybe replace it with our custom permissions system
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' 'rest_framework.permissions.DjangoModelPermissions',
], ],
'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,
} }
# Internationalization # Internationalization

View File

@ -17,12 +17,24 @@ import os
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
from . import * from . import *
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
@ -39,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

View File

@ -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 = [] 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/"

View File

@ -20,8 +20,7 @@ urlpatterns = [
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),
path('logs/', include('logs.urls')), path('api/', include('api.urls')),
path('api/', include('api.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
@ -37,8 +36,8 @@ if "cas" in settings.INSTALLED_APPS:
from cas import views as cas_views from cas import views as cas_views
urlpatterns += [ urlpatterns += [
# Include CAS Client routers # Include CAS Client routers
path('accounts/login/', cas_views.login, name='login'), path('accounts/login/cas/', cas_views.login, name='cas_login'),
path('accounts/logout/', cas_views.logout, name='logout'), path('accounts/logout/cas/', cas_views.logout, name='cas_logout'),
] ]
if "debug_toolbar" in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:

View File

@ -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 %}
@ -75,6 +75,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a> <a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
</li> </li>
<li class="nav-item active">
<a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> {% trans 'Buttons' %}</a>
</li>
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a> <a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
</li> </li>
@ -125,7 +128,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 &mdash; NoteKfet2020 &mdash;
<a href="mailto:tresorie.bde@lists.crans.org" <a href="mailto:{{ "CONTACT_EMAIL" | getenv }}"
class="text-muted">Nous contacter</a> &mdash; class="text-muted">Nous contacter</a> &mdash;
</span> </span>
{% csrf_token %} {% csrf_token %}

View File

@ -0,0 +1,5 @@
{% load crispy_forms_tags %}
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
{% crispy filter.form %}

View File

@ -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>

View File

@ -0,0 +1 @@
{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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}}

View File

@ -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">

View File

@ -32,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