1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 09:58:23 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
5c77b6ebfa Fix tests
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 14:52:34 +02:00
92610c5ffd C'est pas moi
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 13:22:01 +02:00
ddbdd5b5d0 De l'inclusif, partout
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 13:20:17 +02:00
e3ee39ca81 [scripts] Remove README and add link to documentation
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:54:30 +02:00
0c1c845f72 Fresh installations are made with Debian Bullseye
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:41:48 +02:00
edcf42beb6 Update Documentation Copyright
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:24:27 +02:00
3306aed6dc Replace ... by …
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-29 11:22:44 +02:00
a69573ccdb Fix permission that allows users to create OAuth2 apps
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-08-29 11:21:45 +02:00
5a77a66391 Merge branch 'beta' into 'main'
Friendships

See merge request bde/nk20!200
2022-04-13 12:45:06 +02:00
761fc170eb Update Spanish translation 2022-04-13 12:30:22 +02:00
ac23d7eb54 Generated translation files for de/es (but didn't translate anything) 2022-04-13 12:30:22 +02:00
40e7415062 Added translations for friendships 2022-04-13 12:30:22 +02:00
319405d2b1 Added a message to explain what frendships do
Signed-off-by: Nicolas Margulies <nicomarg@crans.org>
2022-04-13 12:30:22 +02:00
633ab88b04 Linting 2022-04-13 12:30:22 +02:00
e29b42eecc Add permissions related to trusting 2022-04-13 12:30:22 +02:00
dc69faaf1d Better user search to add friendships 2022-04-13 12:30:22 +02:00
442a5c5e36 First proro of trusting, with models and front, but no additional permissions 2022-04-13 12:30:22 +02:00
7ab0fec3bc Added trust model 2022-04-13 12:30:22 +02:00
bd4fb23351 Merge branch 'color_survi' into 'main'
switching to survivalist color

See merge request bde/nk20!199
2022-04-12 20:16:55 +02:00
ee22e9b3b6 fixing color to follow the proper theme 2022-04-12 18:33:22 +02:00
19ae616fb4 switching to survivalist color 2022-04-12 17:40:52 +02:00
b7657ec362 Merge branch 'color_ttlsp' into 'main'
Passage des couleur vers ttlsp

See merge request bde/nk20!197
2022-04-05 15:05:41 +02:00
4d03d9460d Passage des couleurs ttlsp 2022-04-05 14:45:41 +02:00
3633f66a87 Merge branch 'beta' into 'main'
Corrections de bugs

See merge request bde/nk20!195
2022-03-09 15:10:37 +01:00
d43fbe7ac6 Merge branch 'harden' into 'beta'
Harden Django project configuration

See merge request bde/nk20!194
2022-03-09 12:30:23 +01:00
df5f9b5f1e Harden Django project configuration
Set session and CSRF cookies as secure for production.
Set HSTS header to let browser remember HTTPS for 1 year.
2022-03-09 12:12:56 +01:00
5f69232560 Merge branch 'beta' into 'main'
Optional scopes + small bug fix

See merge request bde/nk20!193
2022-02-12 14:37:58 +01:00
1eb72044c2 Merge branch 'beta' into 'master'
Changements variés et mineurs

Closes #107 et #91

See merge request bde/nk20!191
2021-12-13 21:16:26 +01:00
ca2b9f061c Merge branch 'beta' into 'master'
Multiples fix, réparation des pots

Closes #75

See merge request bde/nk20!186
2021-10-05 12:02:03 +02:00
82 changed files with 2543 additions and 1385 deletions

View File

@ -55,7 +55,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
(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
(env)$ ./manage.py createsuperuser # Création d'un utilisateur initial (env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
``` ```
6. Enjoy : 6. Enjoy :

View File

@ -26,7 +26,7 @@ class ActivityForm(forms.ModelForm):
clubs = list(Club.objects.filter(PermissionBackend clubs = list(Club.objects.filter(PermissionBackend
.filter_queryset(get_current_request(), Club, "view")).all()) .filter_queryset(get_current_request(), Club, "view")).all())
shuffle(clubs) shuffle(clubs)
self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ","
def clean_organizer(self): def clean_organizer(self):
organizer = self.cleaned_data['organizer'] organizer = self.cleaned_data['organizer']
@ -53,7 +53,7 @@ class ActivityForm(forms.ModelForm):
model=Note, model=Note,
attrs={ attrs={
"api_url": "/api/note/note/", "api_url": "/api/note/note/",
'placeholder': 'Note de l\'événement sur laquelle envoyer les crédits d\'invitation ...' 'placeholder': 'Note de l\'événement sur laquelle envoyer les crédits d\'invitation'
}, },
), ),
"attendees_club": Autocomplete( "attendees_club": Autocomplete(
@ -115,7 +115,7 @@ class GuestForm(forms.ModelForm):
# We don't evaluate the content type at launch because the DB might be not initialized # We don't evaluate the content type at launch because the DB might be not initialized
'api_url_suffix': 'api_url_suffix':
lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteUser).pk), lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteUser).pk),
'placeholder': 'Note ...', 'placeholder': 'Note',
}, },
), ),
} }

View File

@ -126,7 +126,7 @@ class Activity(models.Model):
@transaction.atomic @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Update the activity wiki page each time the activity is updated (validation, change description, ...) Update the activity wiki page each time the activity is updated (validation, change description,)
""" """
if self.date_end < self.date_start: if self.date_end < self.date_start:
raise ValidationError(_("The end date must be after the start date.")) raise ValidationError(_("The end date must be after the start date."))

View File

@ -37,7 +37,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<button class="btn btn-light">{% trans "Return to activity page" %}</button> <button class="btn btn-light">{% trans "Return to activity page" %}</button>
</a> </a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ..."> <input id="alias" type="text" class="form-control" placeholder="Nom/note">
<hr> <hr>

View File

@ -56,13 +56,13 @@ def save_object(sender, instance, **kwargs):
# noinspection PyProtectedMember # noinspection PyProtectedMember
previous = instance._previous previous = instance._previous
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP # Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
request = get_current_request() request = get_current_request()
if request is None: if request is None:
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # 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 # 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 # IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
ip = "127.0.0.1" ip = "127.0.0.1"
username = Alias.normalize(getpass.getuser()) username = Alias.normalize(getpass.getuser())
note = NoteUser.objects.filter(alias__normalized_name=username) note = NoteUser.objects.filter(alias__normalized_name=username)
@ -134,13 +134,13 @@ def delete_object(sender, instance, **kwargs):
if instance._meta.label_lower in EXCLUDED or hasattr(instance, "_no_signal"): if instance._meta.label_lower in EXCLUDED or hasattr(instance, "_no_signal"):
return return
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP # Si un⋅e utilisateur⋅rice est connecté⋅e, on récupère l'utilisateur⋅rice courant⋅e ainsi que son adresse IP
request = get_current_request() request = get_current_request()
if request is None: if request is None:
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # 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 # 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 # IMPORTANT : l'utilisateur⋅rice dans la VM doit être un des alias note du respo info
ip = "127.0.0.1" ip = "127.0.0.1"
username = Alias.normalize(getpass.getuser()) username = Alias.normalize(getpass.getuser())
note = NoteUser.objects.filter(alias__normalized_name=username) note = NoteUser.objects.filter(alias__normalized_name=username)

View File

@ -200,9 +200,9 @@ class MembershipForm(forms.ModelForm):
class Meta: class Meta:
model = Membership model = Membership
fields = ('user', 'date_start') fields = ('user', 'date_start')
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion. # Le champ d'utilisateur⋅rice 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 # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
# et récupère les noms d'utilisateur valides # et récupère les noms d'utilisateur⋅rices valides
widgets = { widgets = {
'user': 'user':
Autocomplete( Autocomplete(
@ -210,7 +210,7 @@ class MembershipForm(forms.ModelForm):
attrs={ attrs={
'api_url': '/api/user/', 'api_url': '/api/user/',
'name_field': 'username', 'name_field': 'username',
'placeholder': 'Nom ...', 'placeholder': 'Nom',
}, },
), ),
'date_start': DatePickerInput(), 'date_start': DatePickerInput(),
@ -227,7 +227,7 @@ class MembershipRolesForm(forms.ModelForm):
attrs={ attrs={
'api_url': '/api/user/', 'api_url': '/api/user/',
'name_field': 'username', 'name_field': 'username',
'placeholder': 'Nom ...', 'placeholder': 'Nom',
}, },
), ),
) )

View File

@ -402,10 +402,10 @@ class Membership(models.Model):
if self.club.parent_club.name == "BDE": if self.club.parent_club.name == "BDE":
parent_membership.roles.set( parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all()) Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all())
elif self.club.parent_club.name == "Kfet": elif self.club.parent_club.name == "Kfet":
parent_membership.roles.set( parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all()) Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
else: else:
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() parent_membership.save()

View File

@ -0,0 +1,53 @@
/**
* On form submit, create a new friendship
*/
function create_trust (e) {
// Do not submit HTML form
e.preventDefault()
// Get data and send to API
const formData = new FormData(e.target)
$.getJSON('/api/note/alias/'+formData.get('trusted') + '/',
function (trusted_alias) {
if ((trusted_alias.note == formData.get('trusting')))
{
addMsg(gettext("You can't add yourself as a friend"), "danger")
return
}
$.post('/api/note/trust/', {
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
trusting: formData.get('trusting'),
trusted: trusted_alias.note
}).done(function () {
// Reload table
$('#trust_table').load(location.pathname + ' #trust_table')
addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
/**
* On click of "delete", delete the alias
* @param button_id:Integer Alias id to remove
*/
function delete_button (button_id) {
$.ajax({
url: '/api/note/trust/' + button_id + '/',
method: 'DELETE',
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
}).done(function () {
addMsg(gettext('Friendship successfully deleted'), 'success')
$('#trust_table').load(location.pathname + ' #trust_table')
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
$(document).ready(function () {
// Attach event
document.getElementById('form_trust').addEventListener('submit', create_trust)
})

View File

@ -25,6 +25,14 @@
</a> </a>
</dd> </dd>
<dt class="col-xl-6">{% trans 'friendships'|capfirst %}</dt>
<dd class="col-xl-6">
<a class="badge badge-secondary" href="{% url 'member:user_trust' user_object.pk %}">
<i class="fa fa-edit"></i>
{% trans 'Manage friendships' %} ({{ user_object.note.trusting.all|length }})
</a>
</dd>
{% if "member.view_profile"|has_perm:user_object.profile %} {% if "member.view_profile"|has_perm:user_object.profile %}
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.section }}</dd> <dd class="col-xl-6">{{ user_object.profile.section }}</dd>

View File

@ -0,0 +1,41 @@
{% extends "member/base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load static django_tables2 i18n %}
{% block profile_content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "Note friendships" %}
</h3>
<div class="card-body">
{% if can_create %}
<form class="input-group" method="POST" id="form_trust">
{% csrf_token %}
<input type="hidden" name="trusting" value="{{ object.note.pk }}">
{%include "autocomplete_model.html" %}
<div class="input-group-append">
<input type="submit" class="btn btn-success" value="{% trans "Add" %}">
</div>
</form>
{% endif %}
</div>
{% render_table trusting %}
</div>
<div class="alert alert-warning card">
{% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming
from your account (while keeping your balance positive). This is
designed to simplify using note kfet transfers to transfer money between
users. The intent is that one person can make all transfers for a group of
friends without needing additional rights among them.
{% endblocktrans %}
</div>
{% endblock %}
{% block extrajavascript %}
<script src="{% static "member/js/trust.js" %}"></script>
<script src="{% static "js/autocomplete_model.js" %}"></script>
{% endblock%}

View File

@ -291,7 +291,7 @@ class TestMemberships(TestCase):
response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict( response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict(
roles=[role.id for role in Role.objects.filter( roles=[role.id for role in Role.objects.filter(
Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).all()], Q(name="Membre de club") | Q(name="Trésorière de club") | Q(name="Bureau de club")).all()],
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.membership.refresh_from_db() self.membership.refresh_from_db()

View File

@ -23,5 +23,6 @@ urlpatterns = [
path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name="user_update_profile"), path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name="user_update_profile"),
path('user/<int:pk>/update_pic/', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"), path('user/<int:pk>/update_pic/', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
] ]

View File

@ -8,6 +8,7 @@ from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
from django.db import transaction from django.db import transaction
from django.db.models import Q, F from django.db.models import Q, F
from django.shortcuts import redirect from django.shortcuts import redirect
@ -18,9 +19,9 @@ from django.views.generic import DetailView, UpdateView, TemplateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from note.models import Alias, NoteUser, NoteClub from note.models import Alias, NoteClub, NoteUser, Trust
from note.models.transactions import Transaction, SpecialTransaction from note.models.transactions import Transaction, SpecialTransaction
from note.tables import HistoryTable, AliasTable from note.tables import HistoryTable, AliasTable, TrustTable
from note_kfet.middlewares import _set_current_request from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
@ -243,6 +244,39 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
return context return context
class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"""
View and manage user trust relationships
"""
model = User
template_name = 'member/profile_trust.html'
context_object_name = 'user_object'
extra_context = {"title": _("Note friendships")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
note = context['object'].note
context["trusting"] = TrustTable(
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
trusting=context["object"].note,
trusted=context["object"].note
))
context["widget"] = {
"name": "trusted",
"attrs": {
"model_pk": ContentType.objects.get_for_model(Alias).pk,
"class": "autocomplete form-control",
"id": "trusted",
"resetable": True,
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
"name_field": "name",
"placeholder": ""
}
}
return context
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
""" """
View and manage user aliases. View and manage user aliases.
@ -786,8 +820,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
ret = super().form_valid(form) ret = super().form_valid(form)
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \ member_role = Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all() \
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \ if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all() \
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all() if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
# Set the same roles as before # Set the same roles as before
if old_membership: if old_membership:
@ -823,7 +857,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
membership.refresh_from_db() membership.refresh_from_db()
if old_membership.exists(): if old_membership.exists():
membership.roles.set(old_membership.get().roles.all()) membership.roles.set(old_membership.get().roles.all())
membership.roles.set(Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all()) membership.roles.set(Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all())
membership.save() membership.save()
return ret return ret

View File

@ -12,7 +12,7 @@ from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from rest_framework.utils import model_meta from rest_framework.utils import model_meta
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
RecurrentTransaction, SpecialTransaction RecurrentTransaction, SpecialTransaction
@ -77,6 +77,22 @@ class NoteUserSerializer(serializers.ModelSerializer):
return str(obj) return str(obj)
class TrustSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Trusts.
The djangorestframework plugin will analyse the model `Trust` and parse all fields in the API.
"""
class Meta:
model = Trust
fields = '__all__'
def validate(self, attrs):
instance = Trust(**attrs)
instance.clean()
return attrs
class AliasSerializer(serializers.ModelSerializer): class AliasSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Aliases. REST API Serializer for Aliases.

View File

@ -2,7 +2,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \ from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet, \
TrustViewSet
def register_note_urls(router, path): def register_note_urls(router, path):
@ -11,6 +12,7 @@ def register_note_urls(router, path):
""" """
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet) router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)

View File

@ -14,8 +14,9 @@ from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSe
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial TrustSerializer
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
@ -56,11 +57,41 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
return queryset.order_by("id") return queryset.order_by("id")
class TrustViewSet(ReadProtectedModelViewSet):
"""
REST Trust View set.
The djangorestframework plugin will get all `Trust` objects, serialize it to JSON with the given serializer,
then render it on /api/note/trust/
"""
queryset = Trust.objects
serializer_class = TrustSerializer
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = ['$trusting__alias__name', '$trusting__alias__normalized_name',
'$trusted__alias__name', '$trusted__alias__normalized_name']
filterset_fields = ['trusting', 'trusting__noteuser__user', 'trusted', 'trusted__noteuser__user']
ordering_fields = ['trusting', 'trusted', ]
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method in ['PUT', 'PATCH']:
# trust relationship can't change people involved
serializer_class.Meta.read_only_fields = ('trusting', 'trusting',)
return serializer_class
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
try:
self.perform_destroy(instance)
except ValidationError as e:
return Response({e.code: str(e)}, status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT)
class AliasViewSet(ReadProtectedModelViewSet): class AliasViewSet(ReadProtectedModelViewSet):
""" """
REST API View set. REST API View set.
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer, The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
then render it on /api/aliases/ then render it on /api/note/aliases/
""" """
queryset = Alias.objects queryset = Alias.objects
serializer_class = AliasSerializer serializer_class = AliasSerializer

View File

@ -26,7 +26,7 @@ class TransactionTemplateForm(forms.ModelForm):
# We don't evaluate the content type at launch because the DB might be not initialized # We don't evaluate the content type at launch because the DB might be not initialized
'api_url_suffix': 'api_url_suffix':
lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk), lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk),
'placeholder': 'Note ...', 'placeholder': 'Note',
}, },
), ),
'amount': AmountInput(), 'amount': AmountInput(),
@ -43,7 +43,7 @@ class SearchTransactionForm(forms.Form):
resetable=True, resetable=True,
attrs={ attrs={
'api_url': '/api/note/alias/', 'api_url': '/api/note/alias/',
'placeholder': 'Note ...', 'placeholder': 'Note',
}, },
), ),
) )
@ -57,7 +57,7 @@ class SearchTransactionForm(forms.Form):
resetable=True, resetable=True,
attrs={ attrs={
'api_url': '/api/note/alias/', 'api_url': '/api/note/alias/',
'placeholder': 'Note ...', 'placeholder': 'Note',
}, },
), ),
) )

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.24 on 2021-09-05 19:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('note', '0005_auto_20210313_1235'),
]
operations = [
migrations.CreateModel(
name='Trust',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('trusted', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusted', to='note.Note', verbose_name='trusted')),
('trusting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusting', to='note.Note', verbose_name='trusting')),
],
options={
'verbose_name': 'friendship',
'verbose_name_plural': 'friendships',
'unique_together': {('trusting', 'trusted')},
},
),
]

View File

@ -1,13 +1,13 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
from .transactions import MembershipTransaction, Transaction, \ from .transactions import MembershipTransaction, Transaction, \
TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction
__all__ = [ __all__ = [
# Notes # Notes
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', 'Alias', 'Trust', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
# Transactions # Transactions
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate', 'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
'RecurrentTransaction', 'SpecialTransaction', 'RecurrentTransaction', 'SpecialTransaction',

View File

@ -217,6 +217,38 @@ class NoteSpecial(Note):
return self.special_type return self.special_type
class Trust(models.Model):
"""
A one-sided trust relationship bertween two users
If another user considers you as your friend, you can transfer money from
them
"""
trusting = models.ForeignKey(
Note,
on_delete=models.CASCADE,
related_name='trusting',
verbose_name=_('trusting')
)
trusted = models.ForeignKey(
Note,
on_delete=models.CASCADE,
related_name='trusted',
verbose_name=_('trusted')
)
class Meta:
verbose_name = _("friendship")
verbose_name_plural = _("friendships")
unique_together = ("trusting", "trusted")
def __str__(self):
return _("Friendship between {trusting} and {trusted}").format(
trusting=str(self.trusting), trusted=str(self.trusted))
class Alias(models.Model): class Alias(models.Model):
""" """
points toward a :model:`note.NoteUser` or :model;`note.NoteClub` instance. points toward a :model:`note.NoteUser` or :model;`note.NoteClub` instance.

View File

@ -20,7 +20,7 @@ class TemplateCategory(models.Model):
""" """
Defined a recurrent transaction category Defined a recurrent transaction category
Example: food, softs, ... Example: food, softs,
""" """
name = models.CharField( name = models.CharField(
verbose_name=_("name"), verbose_name=_("name"),
@ -40,7 +40,7 @@ class TransactionTemplate(models.Model):
""" """
Defined a recurrent transaction Defined a recurrent transaction
associated to selling something (a burger, a beer, ...) associated to selling something (a burger, a beer,)
""" """
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),

View File

@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .models.notes import Alias from .models.notes import Alias, Trust
from .models.transactions import Transaction, TransactionTemplate from .models.transactions import Transaction, TransactionTemplate
from .templatetags.pretty_money import pretty_money from .templatetags.pretty_money import pretty_money
@ -148,6 +148,31 @@ DELETE_TEMPLATE = """
""" """
class TrustTable(tables.Table):
class Meta:
attrs = {
'class': 'table table condensed table-striped',
'id': "trust_table"
}
model = Trust
fields = ("trusted",)
template_name = 'django_tables2/bootstrap4.html'
show_header = False
trusted = tables.Column(attrs={'td': {'class': 'text_center'}})
delete_col = tables.TemplateColumn(
template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('delete')},
attrs={
'td': {
'class': lambda record: 'col-sm-1'
+ (' d-none' if not PermissionBackend.check_perm(
get_current_request(), "note.delete_trust", record)
else '')}},
verbose_name=_("Delete"),)
class AliasTable(tables.Table): class AliasTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {

View File

@ -40,7 +40,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{# User search with autocompletion #} {# User search with autocompletion #}
<div class="card-footer"> <div class="card-footer">
<input class="form-control mx-auto d-block" <input class="form-control mx-auto d-block"
placeholder="{% trans "Name or alias..." %}" type="text" id="note" autofocus /> placeholder="{% trans "Name or alias" %}" type="text" id="note" autofocus />
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
</p> </p>
<p> <p>
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde
est inférieur à 0 € depuis plus de 24h. est inférieur à 0 € depuis plus de 24h.
</p> </p>

View File

@ -9,7 +9,7 @@ Ce mail t'a été envoyé parce que le solde de ta Note Kfet
Ton solde actuel est de {{ note.balance|pretty_money }}. Ton solde actuel est de {{ note.balance|pretty_money }}.
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde
est inférieur à 0 € depuis plus de 24h. est inférieur à 0 € depuis plus de 24h.
Si tu ne comprends pas ton solde, tu peux consulter ton historique Si tu ne comprends pas ton solde, tu peux consulter ton historique

View File

@ -66,7 +66,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option> <option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input class="form-control mx-auto" type="text" id="source_note" placeholder="{% trans "Name or alias..." %}" /> <input class="form-control mx-auto" type="text" id="source_note" placeholder="{% trans "Name or alias" %}" />
<div id="source_me_div"> <div id="source_me_div">
<hr> <hr>
<a class="btn-block btn btn-secondary" href="#" id="source_me" data-turbolinks="false"> <a class="btn-block btn btn-secondary" href="#" id="source_me" data-turbolinks="false">
@ -93,14 +93,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option> <option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input class="form-control mx-auto" type="text" id="dest_note" placeholder="{% trans "Name or alias..." %}" /> <input class="form-control mx-auto" type="text" id="dest_note" placeholder="{% trans "Name or alias" %}" />
<ul class="list-group list-group-flush" id="dest_alias_matched"> <ul class="list-group list-group-flush" id="dest_alias_matched">
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
{# Information on transaction (amount, reason, name,...) #} {# Information on transaction (amount, reason, name,) #}
<div class="col-md" id="external_div"> <div class="col-md" id="external_div">
<div class="card bg-light mb-4"> <div class="card bg-light mb-4">
<div class="card-header"> <div class="card-header">

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="row justify-content-center mb-4"> <div class="row justify-content-center mb-4">
<div class="col-md-10 text-center"> <div class="col-md-10 text-center">
{# Search field , see js #} {# Search field , see js #}
<input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button..." %}" value="{{ request.GET.search }}"> <input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button" %}" value="{{ request.GET.search }}">
<hr> <hr>
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}" data-turbolinks="false">{% trans "New button" %}</a> <a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}" data-turbolinks="false">{% trans "New button" %}</a>
</div> </div>
@ -19,7 +19,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="col-md-12"> <div class="col-md-12">
<div class="card card-border shadow"> <div class="card card-border shadow">
<div class="card-header text-center"> <div class="card-header text-center">
<h5> {% trans "buttons listing "%}</h5> <h5>{% trans "buttons listing"%}</h5>
</div> </div>
<div class="card-body px-0 py-0" id="buttons_table"> <div class="card-body px-0 py-0" id="buttons_table">
{% render_table table %} {% render_table table %}
@ -70,11 +70,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
headers: {"X-CSRFTOKEN": CSRF_TOKEN} headers: {"X-CSRFTOKEN": CSRF_TOKEN}
}) })
.done(function() { .done(function() {
addMsg('{% trans "button successfully deleted "%}','success'); addMsg('{% trans "button successfully deleted"%}','success');
$("#buttons_table").load(location.pathname + "?search=" + $("#search_field").val().replace(" ", "%20") + " #buttons_table"); $("#buttons_table").load(location.pathname + "?search=" + $("#search_field").val().replace(" ", "%20") + " #buttons_table");
}) })
.fail(function() { .fail(function() {
addMsg('{% trans "Unable to delete button "%} #' + button_id, 'danger') addMsg('{% trans "Unable to delete button"%} #' + button_id, 'danger')
}); });
} }

View File

@ -36,7 +36,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir son compte utilisateur" "description": "Voir son compte utilisateur⋅rice"
} }
}, },
{ {
@ -68,7 +68,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir sa propre note d'utilisateur" "description": "Voir sa propre note d'utilisateur⋅rice"
} }
}, },
{ {
@ -116,7 +116,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir les aliases des notes des clubs et des adhérents du club BDE" "description": "Voir les alias des notes des clubs et des adhérent⋅es du club BDE"
} }
}, },
{ {
@ -772,7 +772,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir les adhérents du club" "description": "Voir les adhérent⋅es du club"
} }
}, },
{ {
@ -788,7 +788,7 @@
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un membre à un club" "description": "Ajouter un⋅e membre à un club"
} }
}, },
{ {
@ -852,7 +852,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Modifier n'importe quel utilisateur" "description": "Modifier n'importe quel⋅le utilisateur⋅rice"
} }
}, },
{ {
@ -868,7 +868,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un utilisateur" "description": "Ajouter un⋅e utilisateur⋅rice"
} }
}, },
{ {
@ -1284,7 +1284,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Inscrire un 1A au WEI" "description": "Inscrire un⋅e 1A au WEI"
} }
}, },
{ {
@ -1572,7 +1572,7 @@
"mask": 1, "mask": 1,
"field": "information_json", "field": "information_json",
"permanent": false, "permanent": false,
"description": "Modifier les informations (sondage 1A, ...) d'une inscription WEI" "description": "Modifier les informations (sondage 1A,) d'une inscription WEI"
} }
}, },
{ {
@ -1956,7 +1956,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
"description": "Voir mes activitées passées, même après la fin de l'adhésion BDE" "description": "Voir mes activités passées, même après la fin de l'adhésion BDE"
} }
}, },
{ {
@ -2100,7 +2100,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur" "description": "Voir n'importe quel⋅le utilisateur⋅rice"
} }
}, },
{ {
@ -2228,7 +2228,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une note d'utilisateur" "description": "Créer une note d'utilisateur⋅rice"
} }
}, },
{ {
@ -2276,7 +2276,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir tous les adhérents du club" "description": "Voir toustes les adhérent⋅es du club"
} }
}, },
{ {
@ -2292,7 +2292,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Ajouter un membre à n'importe quel club" "description": "Ajouter un⋅e membre à n'importe quel club"
} }
}, },
{ {
@ -2372,7 +2372,7 @@
"mask": 1, "mask": 1,
"field": "name", "field": "name",
"permanent": false, "permanent": false,
"description": "Modifier le nom d'une activité non validée dont on est l'auteur" "description": "Modifier le nom d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2388,7 +2388,7 @@
"mask": 1, "mask": 1,
"field": "description", "field": "description",
"permanent": false, "permanent": false,
"description": "Modifier la description d'une activité non validée dont on est l'auteur" "description": "Modifier la description d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2404,7 +2404,7 @@
"mask": 1, "mask": 1,
"field": "location", "field": "location",
"permanent": false, "permanent": false,
"description": "Modifier le lieu d'une activité non validée dont on est l'auteur" "description": "Modifier le lieu d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2420,7 +2420,7 @@
"mask": 1, "mask": 1,
"field": "activity_type", "field": "activity_type",
"permanent": false, "permanent": false,
"description": "Modifier le type d'une activité non validée dont on est l'auteur" "description": "Modifier le type d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2436,7 +2436,7 @@
"mask": 1, "mask": 1,
"field": "organizer", "field": "organizer",
"permanent": false, "permanent": false,
"description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur" "description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2452,7 +2452,7 @@
"mask": 1, "mask": 1,
"field": "attendees_club", "field": "attendees_club",
"permanent": false, "permanent": false,
"description": "Modifier le club attendu d'une activité non validée dont on est l'auteur" "description": "Modifier le club attendu d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2468,7 +2468,7 @@
"mask": 1, "mask": 1,
"field": "date_start", "field": "date_start",
"permanent": false, "permanent": false,
"description": "Modifier la date de début d'une activité non validée dont on est l'auteur" "description": "Modifier la date de début d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2484,7 +2484,7 @@
"mask": 1, "mask": 1,
"field": "date_end", "field": "date_end",
"permanent": false, "permanent": false,
"description": "Modifier la date de fin d'une activité non validée dont on est l'auteur" "description": "Modifier la date de fin d'une activité non validée dont on est l'auteur⋅rice"
} }
}, },
{ {
@ -2756,7 +2756,7 @@
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Modifier n'importe quel utilisateur non encore inscrit" "description": "Modifier n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
} }
}, },
{ {
@ -2788,7 +2788,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir tous les alias, y compris ceux des non adhérents" "description": "Voir tous les alias, y compris ceux des non adhérent⋅es"
} }
}, },
{ {
@ -2820,7 +2820,7 @@
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur non encore inscrit" "description": "Voir n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e"
} }
}, },
{ {
@ -2847,12 +2847,12 @@
"auth", "auth",
"user" "user"
], ],
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", "query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent⋅e BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view", "type": "view",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur qui est adhérent BDE" "description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e BDE"
} }
}, },
{ {
@ -2928,7 +2928,7 @@
"application" "application"
], ],
"query": "{\"user\": [\"user\"]}", "query": "{\"user\": [\"user\"]}",
"type": "create", "type": "add",
"mask": 1, "mask": 1,
"field": "", "field": "",
"permanent": true, "permanent": true,
@ -2967,12 +2967,124 @@
"description": "Supprimer une application OAuth2" "description": "Supprimer une application OAuth2"
} }
}, },
{
"model": "permission.permission",
"pk": 190,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusting\": [\"user\", \"note\"]}",
"type": "delete",
"mask": 1,
"field": "",
"permanent": false,
"description": "Supprimer une amitié à sa note"
}
},
{
"model": "permission.permission",
"pk": 191,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusting\": [\"user\", \"note\"]}",
"type": "add",
"mask": 1,
"field": "",
"permanent": false,
"description": "Ajouter une amitié à sa note"
}
},
{
"model": "permission.permission",
"pk": 192,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusting__is_active\": true}",
"type": "add",
"mask": 1,
"field": "",
"permanent": false,
"description": "Ajouter une amitié à une note non bloquée"
}
},
{
"model": "permission.permission",
"pk": 193,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusting__is_active\": true}",
"type": "delete",
"mask": 3,
"field": "",
"permanent": false,
"description": "Supprimer une amitié à une note non bloquée"
}
},
{
"model": "permission.permission",
"pk": 194,
"fields": {
"model": [
"note",
"trust"
],
"query": "{}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toutes les amitiés, y compris celles des non adhérent⋅es"
}
},
{
"model": "permission.permission",
"pk": 195,
"fields": {
"model": [
"note",
"trust"
],
"query": "{\"trusting__noteuser__user\": [\"user\"]}",
"type": "view",
"mask": 1,
"field": "",
"permanent": true,
"description": "Voir ses propres amitiés, pour toujours"
}
},
{
"model": "permission.permission",
"pk": 196,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]",
"type": "add",
"mask": 1,
"field": "",
"permanent": false,
"description": "Transférer de l'argent depuis une note amie en restant positif"
}
},
{ {
"model": "permission.role", "model": "permission.role",
"pk": 1, "pk": 1,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Adh\u00e9rent BDE", "name": "Adh\u00e9rent\u22c5e BDE",
"permissions": [ "permissions": [
1, 1,
2, 2,
@ -3001,7 +3113,11 @@
186, 186,
187, 187,
188, 188,
189 189,
190,
191,
195,
196
] ]
} }
}, },
@ -3010,7 +3126,7 @@
"pk": 2, "pk": 2,
"fields": { "fields": {
"for_club": 2, "for_club": 2,
"name": "Adh\u00e9rent Kfet", "name": "Adh\u00e9rent\u22c5e Kfet",
"permissions": [ "permissions": [
22, 22,
34, 34,
@ -3042,7 +3158,9 @@
158, 158,
159, 159,
160, 160,
179 179,
189,
190
] ]
} }
}, },
@ -3077,7 +3195,7 @@
"pk": 5, "pk": 5,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Pr\u00e9sident\u00b7e de club", "name": "Pr\u00e9sident\u22c5e de club",
"permissions": [ "permissions": [
50, 50,
62, 62,
@ -3091,7 +3209,7 @@
"pk": 6, "pk": 6,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Tr\u00e9sorier\u00b7\u00e8re de club", "name": "Tr\u00e9sorièr\u22c5e de club",
"permissions": [ "permissions": [
59, 59,
19, 19,
@ -3121,7 +3239,7 @@
"pk": 7, "pk": 7,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Pr\u00e9sident\u00b7e BDE", "name": "Pr\u00e9sident\u22c5e BDE",
"permissions": [ "permissions": [
24, 24,
25, 25,
@ -3137,7 +3255,7 @@
"pk": 8, "pk": 8,
"fields": { "fields": {
"for_club": 1, "for_club": 1,
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE", "name": "Tr\u00e9sorièr\u22c5e BDE",
"permissions": [ "permissions": [
23, 23,
24, 24,
@ -3192,7 +3310,10 @@
176, 176,
177, 177,
178, 178,
183 188,
183,
186,
187
] ]
} }
}, },
@ -3386,7 +3507,14 @@
186, 186,
187, 187,
188, 188,
189 189,
190,
191,
192,
193,
194,
195,
196
] ]
} }
}, },
@ -3490,7 +3618,7 @@
"pk": 13, "pk": 13,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Chef de bus", "name": "Chef\u22c5fe de bus",
"permissions": [ "permissions": [
22, 22,
84, 84,
@ -3508,7 +3636,7 @@
"pk": 14, "pk": 14,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Chef d'\u00e9quipe", "name": "Chef\u22c5fe d'\u00e9quipe",
"permissions": [ "permissions": [
22, 22,
84, 84,
@ -3557,7 +3685,7 @@
"pk": 18, "pk": 18,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Adhérent WEI", "name": "Adhérent\u22c5e WEI",
"permissions": [ "permissions": [
77, 77,
87, 87,

View File

@ -59,7 +59,7 @@ class InstancedPermission:
# Force insertion, no data verification, no trigger # Force insertion, no data verification, no trigger
obj._force_save = True obj._force_save = True
# We don't want to trigger any signal (log, ...) # We don't want to trigger any signal (log,)
obj._no_signal = True obj._no_signal = True
Model.save(obj, force_insert=True) Model.save(obj, force_insert=True)
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists() ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
@ -227,7 +227,7 @@ class Permission(models.Model):
def compute_param(value, **kwargs): def compute_param(value, **kwargs):
""" """
A parameter is given by a list. The first argument is the name of the parameter. A parameter is given by a list. The first argument is the name of the parameter.
The parameters are the user, the club, and some classes (Note, ...) The parameters are the user, the club, and some classes (Note,)
If there are more arguments in the list, then attributes are queried. If there are more arguments in the list, then attributes are queried.
For example, ["user", "note", "balance"] will return the balance of the note of the user. For example, ["user", "note", "balance"] will return the balance of the note of the user.
If an argument is a list, then this is interpreted with a function call: If an argument is a list, then this is interpreted with a function call:

View File

@ -36,8 +36,8 @@ class RightsTable(tables.Table):
def render_roles(self, record): def render_roles(self, record):
# If the user has the right to manage the roles, display the link to manage them # If the user has the right to manage the roles, display the link to manage them
roles = record.roles.filter((~(Q(name="Adhérent BDE") roles = record.roles.filter((~(Q(name="Adhérent⋅e BDE")
| Q(name="Adhérent Kfet") | Q(name="Adhérent⋅e Kfet")
| Q(name="Membre de club") | Q(name="Membre de club")
| Q(name="Bureau de club")) | Q(name="Bureau de club"))
& Q(weirole__isnull=True))).all() & Q(weirole__isnull=True))).all()

View File

@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase):
# Create membership to validate permissions # Create membership to validate permissions
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.save() membership.save()
# User is now a member and can now see its own user detail # User is now a member and can now see its own user detail
@ -85,7 +85,7 @@ class OAuth2TestCase(TestCase):
bde = Club.objects.get(name="BDE") bde = Club.objects.get(name="BDE")
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.save() membership.save()
resp = self.client.get(reverse('permission:scopes')) resp = self.client.get(reverse('permission:scopes'))

View File

@ -131,8 +131,8 @@ class RightsView(TemplateView):
special_memberships = Membership.objects.filter( special_memberships = Membership.objects.filter(
date_start__lte=date.today(), date_start__lte=date.today(),
date_end__gte=date.today(), date_end__gte=date.today(),
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE") ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE")
| Q(name="Adhérent Kfet") | Q(name="Adhérent⋅e Kfet")
| Q(name="Membre de club") | Q(name="Membre de club")
| Q(name="Bureau de club")) | Q(name="Bureau de club"))
& Q(weirole__isnull=True))))\ & Q(weirole__isnull=True))))\

View File

@ -352,7 +352,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
membership._soge = True membership._soge = True
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
membership.save() membership.save()
if join_kfet: if join_kfet:
@ -366,7 +366,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
membership._soge = True membership._soge = True
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.roles.add(Role.objects.get(name="Adhérent⋅e Kfet"))
membership.save() membership.save()
if soge: if soge:

View File

@ -174,7 +174,7 @@ class SogeCreditForm(forms.ModelForm):
attrs={ attrs={
'api_url': '/api/user/', 'api_url': '/api/user/',
'name_field': 'username', 'name_field': 'username',
'placeholder': 'Nom ...', 'placeholder': 'Nom',
}, },
), ),
} }

View File

@ -358,7 +358,7 @@ class SogeCredit(models.Model):
def invalidate(self): def invalidate(self):
""" """
Invalidating a Société générale delete the transaction of the bank if it was already created. Invalidating a Société générale delete the transaction of the bank if it was already created.
Treasurers must know what they do, With Great Power Comes Great Responsibility... Treasurers must know what they do, With Great Power Comes Great Responsibility
""" """
if self.valid: if self.valid:
self.credit_transaction.valid = False self.credit_transaction.valid = False
@ -422,7 +422,7 @@ class SogeCredit(models.Model):
""" """
Deleting a SogeCredit is equivalent to say that the Société générale didn't pay. Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
Treasurers must know what they do, this is difficult to undo this operation. Treasurers must know what they do, this is difficult to undo this operation.
With Great Power Comes Great Responsibility... With Great Power Comes Great Responsibility
""" """
total_fee = sum(transaction.total for transaction in self.transactions.all() if not transaction.valid) total_fee = sum(transaction.total for transaction in self.transactions.all() if not transaction.valid)

View File

@ -29,7 +29,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3> </h3>
<div class="card-body"> <div class="card-body">
<div class="input-group"> <div class="input-group">
<input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note ..."> <input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note">
<div class="input-group-append"> <div class="input-group-append">
<button id="add_sogecredit" class="btn btn-success" data-toggle="modal" data-target="#add-sogecredit-modal">{% trans "Add" %}</button> <button id="add_sogecredit" class="btn btn-success" data-toggle="modal" data-target="#add-sogecredit-modal">{% trans "Add" %}</button>
</div> </div>

View File

@ -45,7 +45,7 @@ class WEIRegistrationForm(forms.ModelForm):
attrs={ attrs={
'api_url': '/api/user/', 'api_url': '/api/user/',
'name_field': 'username', 'name_field': 'username',
'placeholder': 'Nom ...', 'placeholder': 'Nom ',
}, },
), ),
"birth_date": DatePickerInput(options={'minDate': '1900-01-01', "birth_date": DatePickerInput(options={'minDate': '1900-01-01',
@ -74,7 +74,7 @@ class WEIChooseBusForm(forms.Form):
queryset=WEIRole.objects.filter(~Q(name="1A")), queryset=WEIRole.objects.filter(~Q(name="1A")),
label=_("WEI Roles"), label=_("WEI Roles"),
help_text=_("Select the roles that you are interested in."), help_text=_("Select the roles that you are interested in."),
initial=WEIRole.objects.filter(name="Adhérent WEI").all(), initial=WEIRole.objects.filter(name="Adhérent⋅e WEI").all(),
widget=CheckboxSelectMultiple(), widget=CheckboxSelectMultiple(),
) )
@ -130,14 +130,14 @@ class WEIMembershipForm(forms.ModelForm):
Bus, Bus,
attrs={ attrs={
'api_url': '/api/wei/bus/', 'api_url': '/api/wei/bus/',
'placeholder': 'Bus ...', 'placeholder': 'Bus',
} }
), ),
"team": Autocomplete( "team": Autocomplete(
BusTeam, BusTeam,
attrs={ attrs={
'api_url': '/api/wei/team/', 'api_url': '/api/wei/team/',
'placeholder': 'Équipe ...', 'placeholder': 'Équipe',
}, },
resetable=True, resetable=True,
), ),
@ -167,7 +167,7 @@ class BusForm(forms.ModelForm):
WEIClub, WEIClub,
attrs={ attrs={
'api_url': '/api/wei/club/', 'api_url': '/api/wei/club/',
'placeholder': 'WEI ...', 'placeholder': 'WEI',
}, },
), ),
} }
@ -182,7 +182,7 @@ class BusTeamForm(forms.ModelForm):
Bus, Bus,
attrs={ attrs={
'api_url': '/api/wei/bus/', 'api_url': '/api/wei/bus/',
'placeholder': 'Bus ...', 'placeholder': 'Bus',
}, },
), ),
"color": ColorWidget(), "color": ColorWidget(),

View File

@ -84,5 +84,5 @@ class Command(BaseCommand):
s += sep + user.profile.section_generated s += sep + user.profile.section_generated
s += sep + bus.name s += sep + bus.name
s += sep + (team.name if team else "--") s += sep + (team.name if team else "--")
s += sep + ", ".join(role.name for role in membership.roles.filter(~Q(name="Adhérent WEI")).all()) s += sep + ", ".join(role.name for role in membership.roles.filter(~Q(name="Adhérent⋅e WEI")).all())
self.stdout.write(s) self.stdout.write(s)

View File

@ -153,7 +153,7 @@ class BusTeam(models.Model):
class WEIRole(Role): class WEIRole(Role):
""" """
A Role for the WEI can be bus chief, team chief, free electron, ... A Role for the WEI can be bus chief, team chief, free electron,
""" """
class Meta: class Meta:
@ -258,7 +258,7 @@ class WEIRegistration(models.Model):
@property @property
def information(self): def information(self):
""" """
The information about the registration (the survey for the new members, the bus for the older members, ...) The information about the registration (the survey for the new members, the bus for the older members,)
are stored in a dictionary that can evolve following the years. The dictionary is stored as a JSON string. are stored in a dictionary that can evolve following the years. The dictionary is stored as a JSON string.
""" """
return json.loads(self.information_json) return json.loads(self.information_json)

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block profile_content %} {% block profile_content %}
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note/bus/équipe ..."> <input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note/bus/équipe">
<hr> <hr>
<div id="memberships_table"> <div id="memberships_table">
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-footer text-center"> <div class="card-footer text-center">
<a href="{% url 'wei:wei_registrations' pk=club.pk %}"> <a href="{% url 'wei:wei_registrations' pk=club.pk %}">
<button class="btn btn-block btn-info">{% trans "View unvalidated registrations..." %}</button> <button class="btn btn-block btn-info">{% trans "View unvalidated registrations" %}</button>
</a> </a>
<hr> <hr>
<a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false"> <a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false">

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block profile_content %} {% block profile_content %}
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note ..."> <input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note">
<hr> <hr>
<div id="registrations_table"> <div id="registrations_table">
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-footer text-center"> <div class="card-footer text-center">
<a href="{% url 'wei:wei_memberships' pk=club.pk %}"> <a href="{% url 'wei:wei_memberships' pk=club.pk %}">
<button class="btn btn-block btn-info">{% trans "View validated memberships..." %}</button> <button class="btn btn-block btn-info">{% trans "View validated memberships" %}</button>
</a> </a>
</div> </div>
</div> </div>

View File

@ -519,7 +519,7 @@ class TestWEIRegistration(TestCase):
emergency_contact_phone='+33600000000', emergency_contact_phone='+33600000000',
bus=[self.bus.id], bus=[self.bus.id],
team=[self.team.id], team=[self.team.id],
roles=[role.id for role in WEIRole.objects.filter(name="Adhérent WEI").all()], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()],
information_json=self.registration.information_json, information_json=self.registration.information_json,
) )
) )
@ -573,7 +573,7 @@ class TestWEIRegistration(TestCase):
emergency_contact_phone='+33600000000', emergency_contact_phone='+33600000000',
bus=[self.bus.id], bus=[self.bus.id],
team=[self.team.id], team=[self.team.id],
roles=[role.id for role in WEIRole.objects.filter(name="Adhérent WEI").all()], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()],
information_json=self.registration.information_json, information_json=self.registration.information_json,
) )
) )

View File

@ -916,7 +916,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form["team"].initial = BusTeam.objects.get(pk=information["preferred_team_pk"][0]) form["team"].initial = BusTeam.objects.get(pk=information["preferred_team_pk"][0])
if "preferred_roles_pk" in information: if "preferred_roles_pk" in information:
form["roles"].initial = WEIRole.objects.filter( form["roles"].initial = WEIRole.objects.filter(
Q(pk__in=information["preferred_roles_pk"]) | Q(name="Adhérent WEI") Q(pk__in=information["preferred_roles_pk"]) | Q(name="Adhérent⋅e WEI")
).all() ).all()
return form return form
@ -1008,7 +1008,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(WEIRole.objects.get(name="Adhérent WEI")) membership.roles.add(WEIRole.objects.get(name="Adhérent⋅e WEI"))
return super().form_valid(form) return super().form_valid(form)
@ -1235,7 +1235,7 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView):
raise Http404 raise Http404
wei = wei.get() wei = wei.get()
qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True) qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True)
qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works... qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works
if qs.exists(): if qs.exists():
return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, )) return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, ))
return reverse_lazy('wei:wei_1A_list', args=(wei.pk, )) return reverse_lazy('wei:wei_1A_list', args=(wei.pk, ))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -23,18 +23,18 @@ Pages de l'API
Il suffit d'ajouter le préfixe ``/api/`` pour arriver sur ces pages. Il suffit d'ajouter le préfixe ``/api/`` pour arriver sur ces pages.
* `models <basic#type-de-contenu>`_ : liste des différents modèles enregistrés en base de données * `models <basic#type-de-contenu>`_ : liste des différents modèles enregistrés en base de données
* `user <basic#utilisateur>`_ : liste des différents utilisateurs enregistrés * `user <basic#utilisateur>`_ : liste des différent⋅es utilisateur⋅rices enregistrés
* `members/profile <member#profil-utilisateur>`_ : liste des différents profils associés à des utilisateurs * `members/profile <member#profil-utilisateur>`_ : liste des différents profils associés à des utilisateurs
* `members/club <member#club>`_ : liste des différents clubs enregistrés * `members/club <member#club>`_ : liste des différents clubs enregistrés
* `members/membership <member#adhesion>`_ : liste des adhésions enregistrées * `members/membership <member#adhesion>`_ : liste des adhésions enregistrées
* `activity/activity <activity#activite>`_ : liste des activités recensées * `activity/activity <activity#activite>`_ : liste des activités recensées
* `activity/type <activity#type-d-activite>`_ : liste des différents types d'activités : pots, soirées de club, ... * `activity/type <activity#type-d-activite>`_ : liste des différents types d'activités : pots, soirées de club,
* `activity/guest <activity#invite>`_ : liste des personnes invitées lors d'une activité * `activity/guest <activity#invite>`_ : liste des personnes invitées lors d'une activité
* `activity/entry <activity#entree>`_ : liste des entrées effectuées lors des activités * `activity/entry <activity#entree>`_ : liste des entrées effectuées lors des activités
* `note/note <note#note>`_ : liste des notes enregistrées * `note/note <note#note>`_ : liste des notes enregistrées
* `note/alias <note#alias>`_ : liste des alias enregistrés * `note/alias <note#alias>`_ : liste des alias enregistrés
* `note/consumer <note#consommateur>`_ : liste des alias enregistrés avec leur note associée * `note/consumer <note#consommateur>`_ : liste des alias enregistrés avec leur note associée
* `note/transaction/category <note#categorie-de-transaction>`_ : liste des différentes catégories de boutons : soft, alcool, ... * `note/transaction/category <note#categorie-de-transaction>`_ : liste des différentes catégories de boutons : soft, alcool,
* `note/transaction/transaction <note#transaction>`_ : liste des transactions effectuées * `note/transaction/transaction <note#transaction>`_ : liste des transactions effectuées
* `note/transaction/template <note#modele-de-transaction>`_ : liste des boutons enregistrés * `note/transaction/template <note#modele-de-transaction>`_ : liste des boutons enregistrés
* `treasury/invoice <treasury#facture>`_ : liste des factures générées * `treasury/invoice <treasury#facture>`_ : liste des factures générées
@ -69,7 +69,7 @@ S'authentifier
L'authentification peut se faire soit par session en se connectant via la page de connexion classique, L'authentification peut se faire soit par session en se connectant via la page de connexion classique,
soit via un jeton d'authentification. Le jeton peut se récupérer via la page de son propre compte, en cliquant soit via un jeton d'authentification. Le jeton peut se récupérer via la page de son propre compte, en cliquant
sur le bouton « `Accès API <https://note.crans.org/accounts/manage-auth-token/>`_ ». Il peut être révoqué et regénéré sur le bouton « `Accès API <https://note.crans.org/accounts/manage-auth-token/>`_ ». Il peut être révoqué et régénéré
en un clic. en un clic.
Pour s'authentifier via ce jeton, il faut ajouter l'en-tête ``Authorization: Token <TOKEN>`` aux paramètres HTTP. Pour s'authentifier via ce jeton, il faut ajouter l'en-tête ``Authorization: Token <TOKEN>`` aux paramètres HTTP.
@ -111,7 +111,7 @@ Trois types de filtres sont implémentés :
Les filtres disponibles sont indiqués sur chacune des pages de documentation. Les filtres disponibles sont indiqués sur chacune des pages de documentation.
Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés. Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur⋅rice a le droit de voir sont affichés.
Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données. Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données.
Une requête à l'adresse ``/api/<model>/<pk>/`` affiche directement les informations du modèle demandé au format JSON. Une requête à l'adresse ``/api/<model>/<pk>/`` affiche directement les informations du modèle demandé au format JSON.
@ -120,14 +120,15 @@ POST
~~~~ ~~~~
Une requête POST permet d'ajouter des éléments. Cette requête n'est possible que sur la page ``/api/<model>/``, Une requête POST permet d'ajouter des éléments. Cette requête n'est possible que sur la page ``/api/<model>/``,
la requête POST n'est pas supportée sur les pages de détails (car cette requête permet ... l'ajout). la requête POST n'est pas supportée sur les pages de détails (car cette requête permet l'ajout).
Des exceptions sont faites sur certaines pages : les pages de logs et de contenttypes sont en lecture uniquement. Des exceptions sont faites sur certaines pages : les pages de logs et de contenttypes sont en lecture uniquement.
Les formats supportés sont multiples : ``application/json``, ``application/x-www-url-encoded``, ``multipart/form-data``. Les formats supportés sont multiples : ``application/json``, ``application/x-www-url-encoded``, ``multipart/form-data``.
Cela facilite l'envoi de requêtes. Le module construit ensuite l'instance du modèle et le sauvegarde dans la base de Cela facilite l'envoi de requêtes. Le module construit ensuite l'instance du modèle et le sauvegarde dans la base de
données. L'application ``permission`` s'assure que l'utilisateur à le droit de faire ce type de modification. La réponse données. L'application ``permission`` s'assure que l'utilisateur⋅rice a le droit de faire ce type de modification.
renvoyée est l'objet enregistré au format JSON si l'ajout s'est bien déroulé, sinon un message d'erreur au format JSON. La réponse renvoyée est l'objet enregistré au format JSON si l'ajout s'est bien déroulé, sinon un message d'erreur au
format JSON.
PATCH PATCH
~~~~~ ~~~~~
@ -205,10 +206,10 @@ Une reqête OPTIONS affiche l'ensemble des opérations possibles sur un modèle
* ``<METHOD>`` est le type de requête HTTP supporté (pour modification, inclus dans {``POST``, ``PUT``, ``PATCH``}). * ``<METHOD>`` est le type de requête HTTP supporté (pour modification, inclus dans {``POST``, ``PUT``, ``PATCH``}).
* ``<FIELD_NAME>`` est le nom du champ dans le modèle concerné (exemple : ``id``) * ``<FIELD_NAME>`` est le nom du champ dans le modèle concerné (exemple : ``id``)
* ``<TYPE>`` représente le type de données : ``integer``, ``string``, ``date``, ``choice``, ``field`` (pour les clés étrangères), ... * ``<TYPE>`` représente le type de données : ``integer``, ``string``, ``date``, ``choice``, ``field`` (pour les clés étrangères),
* ``<REQUIRED>`` est un booléen indiquant si le champ est requis dans le modèle ou s'il peut être nul/vide. * ``<REQUIRED>`` est un booléen indiquant si le champ est requis dans le modèle ou s'il peut être nul/vide.
* ``<READ_ONLY>`` est un booléen indiquant si le champ est accessible en lecture uniquement. * ``<READ_ONLY>`` est un booléen indiquant si le champ est accessible en lecture uniquement.
* ``<LABEL>`` représente le label du champ, son nom traduit, qui s'affiche dans le formulaire accessible sur l'API Web. * ``<LABEL>`` représente le label du champ, son nom traduit, qui s'affiche dans le formulaire accessible sur l'API Web.
Des contraintes peuvent s'ajouter à cela selon les champs : taille maximale de chaînes de caractères, valeurs minimales Des contraintes peuvent s'ajouter à cela selon les champs : taille maximale de chaînes de caractères, valeurs minimales
et maximales pour les entiers ... et maximales pour les entiers

View File

@ -135,7 +135,7 @@ Options
"required": false, "required": false,
"read_only": false, "read_only": false,
"label": "Pay\u00e9", "label": "Pay\u00e9",
"help_text": "Indique si l'utilisateur per\u00e7oit un salaire." "help_text": "Indique si l'utilisateur⋅rice per\u00e7oit un salaire."
}, },
"ml_events_registration": { "ml_events_registration": {
"type": "choice", "type": "choice",

View File

@ -507,7 +507,7 @@ Options
"required": false, "required": false,
"read_only": false, "read_only": false,
"label": "Premi\u00e8re ann\u00e9e", "label": "Premi\u00e8re ann\u00e9e",
"help_text": "Indique si l'utilisateur est nouveau dans l'\u00e9cole." "help_text": "Indique si l'utilisateur⋅rice est nouvelleau dans l'\u00e9cole."
}, },
"information_json": { "information_json": {
"type": "string", "type": "string",
@ -520,7 +520,7 @@ Options
"type": "field", "type": "field",
"required": true, "required": true,
"read_only": false, "read_only": false,
"label": "Utilisateur" "label": "Utilisateur⋅rice"
}, },
"wei": { "wei": {
"type": "field", "type": "field",

View File

@ -3,20 +3,20 @@ Application Activités
L'application activités gère les différentes activités liées au BDE. Elle permet entre autres de créer des activités qui L'application activités gère les différentes activités liées au BDE. Elle permet entre autres de créer des activités qui
peuvent être diffusées via des calendriers ou la mailing list d'événements. Elle permet aussi de réguler l'accès aux peuvent être diffusées via des calendriers ou la mailing list d'événements. Elle permet aussi de réguler l'accès aux
événements, en s'assurant que leur note est positive. Elle permet enfin de gérer les invités. événements, en s'assurant que leur note est positive. Elle permet enfin de gérer les invité⋅es.
Modèles Modèles
------- -------
L'application comporte 5 modèles : activités, types d'activité, invités, entrées et transactions d'invitation. L'application comporte 5 modèles : activités, types d'activité, invité⋅es, entrées et transactions d'invitation.
Types d'activité Types d'activité
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
Les activités sont triées par type (pots, soirées de club, ...), et chaque type regroupe diverses informations : Les activités sont triées par type (pots, soirées de club,), et chaque type regroupe diverses informations :
* Nom du type * Nom du type
* Possibilité d'inviter des non-adhérents (booléen) * Possibilité d'inviter des non-adhérent⋅es (booléen)
* Prix d'invitation (entier, centimes à débiter sur la note de l'hôte) * Prix d'invitation (entier, centimes à débiter sur la note de l'hôte)
Activités Activités
@ -26,7 +26,7 @@ Le modèle d'activité regroupe les informations liées à l'activité même :
* Nom de l'activité * Nom de l'activité
* Description de l'activité * Description de l'activité
* Créateur, personne qui a proposé l'activité * Créateur⋅rice, personne qui a proposé l'activité
* Club ayant organisé l'activité * Club ayant organisé l'activité
* Note sur laquelle verser les crédits d'invitation (peut être nul si non concerné) * Note sur laquelle verser les crédits d'invitation (peut être nul si non concerné)
* Club invité (généralement le club Kfet) * Club invité (généralement le club Kfet)
@ -38,19 +38,19 @@ Le modèle d'activité regroupe les informations liées à l'activité même :
Entrées Entrées
~~~~~~~ ~~~~~~~
Une instance de ce modèle est créé dès que quelqu'un est inscrit à l'activité. Sont stockées les informations suivantes : Une instance de ce modèle est créé dès que quelqu'un⋅e est inscrit⋅e à l'activité. Sont stockées les informations suivantes :
* Activité concernée (clé étrangère) * Activité concernée (clé étrangère)
* Heure d'entrée * Heure d'entrée
* Note de la personne entrée, ou hôte s'il s'agit d'un invité (clé étrangère vers ``NoteUser``) * Note de la personne entrée, ou hôte s'il s'agit d'un⋅e invité⋅e (clé étrangère vers ``NoteUser``)
* Invité (``OneToOneField`` vers ``Guest``, ``None`` si c'est la personne elle-même qui rentre et non son invité) * Invité⋅e (``OneToOneField`` vers ``Guest``, ``None`` si c'est la personne elle-même qui rentre et non saon invité⋅e)
Il n'est pas possible de créer une entrée si la note est en négatif. Il n'est pas possible de créer une entrée si la note est en négatif.
Invités Invité⋅es
~~~~~~~ ~~~~~~~~~
Les adhérents ont la possibilité d'inviter des amis. Pour cela, les différentes informations sont enregistrées : Les adhérent⋅es ont la possibilité d'inviter des ami⋅es. Pour cela, les différentes informations sont enregistrées :
* Activité concernée (clé étrangère) * Activité concernée (clé étrangère)
* Nom de famille * Nom de famille
@ -60,7 +60,7 @@ Les adhérents ont la possibilité d'inviter des amis. Pour cela, les différent
Certaines contraintes s'appliquent : Certaines contraintes s'appliquent :
* Une personne ne peut pas être invitée plus de 5 fois par an (coupe nom/prénom) * Une personne ne peut pas être invitée plus de 5 fois par an (coupe nom/prénom)
* Un adhérent ne peut pas inviter plus de 3 personnes par activité. * Un⋅e adhérent⋅e ne peut pas inviter plus de 3 personnes par activité.
Transactions d'invitation Transactions d'invitation
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
@ -83,15 +83,15 @@ UI
Création d'activités Création d'activités
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
N'importe quel adhérent Kfet peut suggérer l'ajout d'une activité via un formulaire. N'importe quel⋅le adhérent⋅e Kfet peut suggérer l'ajout d'une activité via un formulaire.
Gestion des activités Gestion des activités
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
Les ayant-droit (Res[pot] et respos infos) peuvent valider les activités proposées. Ils peuvent également la modifier Les ayant-droit (Res[pot] et respos infos) peuvent valider les activités proposées. Ils peuvent également la modifier
si besoin. Ils peuvent enfin la déclarer ouvertes pour lancer l'accès aux entrées. si besoin. Iels peuvent enfin la déclarer ouverte pour lancer l'accès aux entrées.
N'importe qui peut inviter des amis non adhérents, tant que les contraintes de nombre (un adhérent n'invite pas plus de N'importe qui peut inviter des ami⋅es non adhérent⋅es, tant que les contraintes de nombre (un⋅e adhérent⋅e n'invite pas plus de
trois personnes par activité et une personne ne peut pas être invitée plus de 5 fois par an). L'invitation est trois personnes par activité et une personne ne peut pas être invitée plus de 5 fois par an). L'invitation est
facturée à l'entrée. facturée à l'entrée.
@ -99,12 +99,12 @@ Entrées aux soirées
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
L'interface d'entrées est simple et ergonomique. Elle contient un champ de texte. À chaque fois que le champ est L'interface d'entrées est simple et ergonomique. Elle contient un champ de texte. À chaque fois que le champ est
modifié, un tableau est affiché comprenant la liste des invités et des adhérents dont le prénom, le nom ou un alias modifié, un tableau est affiché comprenant la liste des invité⋅es et des adhérent⋅es dont le prénom, le nom ou un alias
de la note est acceptée par le texte entré. de la note est acceptée par le texte entré.
En cliquant sur la ligne de la personne qui souhaite rentrée, s'il s'agit d'un adhérent, alors la personne est comptée En cliquant sur la ligne de la personne qui souhaite rentrer, s'il s'agit d'un⋅e adhérent⋅e, alors la personne est comptée
comme entrée à l'activité, sous réserve que sa note soit positive. S'il s'agit d'un invité, alors 3 boutons comme entrée à l'activité, sous réserve que sa note soit positive. S'il s'agit d'un⋅e invité⋅e, alors 3 boutons
apparaîssent, afin de régler la taxe d'invitation : l'un prélève directement depuis la note de l'hôte, les deux autres apparaîssent, afin de régler la taxe d'invitation : l'un prélève directement depuis la note de l'hôte, les deux autres
permettent un paiement par espèces ou par carte bancaire. En réalité, les deux derniers boutons enregistrent permettent un paiement par espèces ou par carte bancaire. En réalité, les deux derniers boutons enregistrent
automatiquement un crédit sur la note de l'hôte, puis une transaction (de type ``GuestTransaction``) est faite depuis automatiquement un crédit sur la note de l'hôte, puis une transaction (de type ``GuestTransaction``) est faite depuis
la note de l'hôte vers la note de l'organisateur de l'événement. la note de l'hôte vers la note du club organisateur de l'événement.

View File

@ -1,5 +1,5 @@
Applications de la NoteKfet2020 Applications de la Note Kfet 2020
=============================== =================================
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
@ -15,27 +15,26 @@ Applications de la NoteKfet2020
treasury treasury
wei wei
La NoteKfet est un projet Django, décomposé en applications. La Note Kfet 2020 est un projet Django, décomposé en applications.
Certaines Applications sont développées uniquement pour ce projet, et sont indispensables, Certaines applications sont développées uniquement pour ce projet, et sont indispensables,
d'autres sont packagesé et sont installées comme dépendances. d'autres sont packagées et sont installées comme dépendances.
Enfin des fonctionnalités annexes ont été rajouté, mais ne sont pas essentiel au déploiement de la NoteKfet; Enfin, des fonctionnalités annexes ont été rajoutées, mais ne sont pas essentielles au déploiement de la Note Kfet 2020. Leur usage est cependant recommandé.
leur usage est cependant recommandé.
Le front utilise le framework Bootstrap4 et quelques morceaux de javascript custom. L'affichage Web utilise le framework Bootstrap4 et quelques morceaux de JavaScript personnalisés.
Applications indispensables Applications indispensables
--------------------------- ---------------------------
* ``note_kfet`` : * ``note_kfet`` :
Application "projet" de django, c'est ici que la config de la note est gérée. Application "projet" de django, c'est ici que la configuration de la note est gérée.
* `Member <member>`_ : * `Member <member>`_ :
Gestion des profils d'utilisateurs, des clubs et de leur membres. Gestion des profils d'utilisateur⋅rices, des clubs et de leur membres.
* `Note <note>`_ : * `Note <note>`_ :
Les notes associés a des utilisateurs ou des clubs. Les notes associées à des utilisateur⋅rices ou des clubs.
* `Activity <activity>`_ : * `Activity <activity>`_ :
La gestion des Activités (créations, gestion, entrée...) La gestion des activités (créations, gestion, entrées,…)
* `Permission <permission>`_ : * `Permission <permission>`_ :
Backend de droits, limites les pouvoirs des utilisateurs Backend de droits, limites les pouvoirs des utilisateur⋅rices
* `API <../api>`_ : * `API <../api>`_ :
API REST de la note, est notamment utilisée pour rendre la note dynamique API REST de la note, est notamment utilisée pour rendre la note dynamique
(notamment la page de conso) (notamment la page de conso)
@ -52,9 +51,9 @@ Applications packagées
`<https://django-polymorphic.readthedocs.io/en/stable/>`_ `<https://django-polymorphic.readthedocs.io/en/stable/>`_
* ``crispy_forms`` * ``crispy_forms``
Utiliser pour générer des forms avec bootstrap4 Utiliser pour générer des formulairess avec Bootstrap4
* ``django_tables2`` * ``django_tables2``
utiliser pour afficher des tables de données et les formater, en python plutôt qu'en HTML. utiliser pour afficher des tables de données et les formater, en Python plutôt qu'en HTML.
* ``restframework`` * ``restframework``
Base de l'`API <../api>`_. Base de l'`API <../api>`_.
@ -63,11 +62,11 @@ Applications facultatives
* `Logs <logs>`_ * `Logs <logs>`_
Enregistre toute les modifications effectuées en base de donnée. Enregistre toute les modifications effectuées en base de donnée.
* ``cas-server`` * ``cas-server``
Serveur central d'authenfication, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client. Serveur central d'authentification, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client.
* `Script <https://gitlab.crans.org/bde/nk20-scripts>`_ * `Scripts <https://gitlab.crans.org/bde/nk20-scripts>`_
Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc ... Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc
* `Treasury <treasury>`_ : * `Treasury <treasury>`_ :
Interface de gestion pour les trésoriers, émission de facture, remise de chèque, statistiques ... Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques
* `WEI <wei>`_ : * `WEI <wei>`_ :
Interface de gestion du WEI. Interface de gestion du WEI.

View File

@ -6,22 +6,22 @@ Chaque modification effectuée sur un modèle est enregistrée dans la base dans
Dès qu'un modèle veut être sauvegardé, deux signaux sont envoyés dans ``logs.signals`` : un avant et un après Dès qu'un modèle veut être sauvegardé, deux signaux sont envoyés dans ``logs.signals`` : un avant et un après
la sauvegarde. la sauvegarde.
En pré-sauvegarde, on récupère l'ancienne version du modèle, si elle existe. En pré-sauvegarde, on récupère l'ancienne version du modèle, si elle existe.
En post-sauvegarde, on récupère l'utilisateur et l'IP courants (voir ci-dessous), on convertit les modèles en JSON En post-sauvegarde, on récupère l'utilisateur⋅rice et l'IP courant⋅es (voir ci-dessous), on convertit les modèles en JSON
et on enregistre une entrée ``Changelog`` dans la base de données. et on enregistre une entrée ``Changelog`` dans la base de données.
Pour récupérer l'utilisateur et son IP, le middleware ``logs.middlewares.LogsMiddlewares`` récupère à chaque requête Pour récupérer l'utilisateur⋅rice et son IP, le middleware ``logs.middlewares.LogsMiddlewares`` récupère à chaque requête
l'utilisateur et l'adresse IP, et les stocke dans le processus courant, afin qu'ils puissent être l'utilisateur⋅rice et l'adresse IP, et les stocke dans le processus courant, afin qu'ils puissent être
récupérés par les signaux. récupérés par les signaux.
Si jamais la modification ne provient pas d'une requête Web, on suppose qu'elle vient d'une instruction Si jamais la modification ne provient pas d'une requête Web, on suppose qu'elle vient d'une instruction
lancée avec ``manage.py``. lancée avec ``manage.py``.
On récupère alors le nom de l'utilisateur dans l'interface de commandes, et si une note est associée à cet alias, On récupère alors le nom de l'utilisateur⋅rice dans l'interface de commandes, et si une note est associée à cet alias,
alors on considère que c'est le détenteur de la note qui a effectué cette modification, sur l'adresse IP ``127.0.0.1``. alors on considère que c'est le détenteur de la note qui a effectué cette modification, sur l'adresse IP ``127.0.0.1``.
Sinon, le champ est laissé à ``None``. Sinon, le champ est laissé à ``None``.
Une entrée de ``Changelog`` contient les informations suivantes : Une entrée de ``Changelog`` contient les informations suivantes :
* Utilisateur (``ForeignKey`` vers ``User``, nullable) * Utilisateur⋅rice (``ForeignKey`` vers ``User``, nullable)
* Adresse IP (``GenericIPAddressField``) * Adresse IP (``GenericIPAddressField``)
* Type de modèle enregistré (``ForeignKey`` vers ``Model``) * Type de modèle enregistré (``ForeignKey`` vers ``Model``)
* Identifiant ``pk`` de l'instance enregistrée (``CharField``) * Identifiant ``pk`` de l'instance enregistrée (``CharField``)
@ -54,4 +54,4 @@ Graphe
~~~~~~ ~~~~~~
.. image:: ../_static/img/graphs/logs.svg .. image:: ../_static/img/graphs/logs.svg
:alt: Logs graphe :alt: Logs graph

View File

@ -1,65 +1,65 @@
Application Member Application Member
================== ==================
L'application ``member`` s'occcupe de la gestion des utilisateurs enregistrés. L'application ``member`` s'occcupe de la gestion des utilisateur⋅rices enregistré⋅es.
Le model d'utilisateur ``django.contrib.auth.model.User`` est complété par un ``Profile`` utilisateur. Le model d'utilisateur⋅rice ``django.contrib.auth.model.User`` est complété par un ``Profile`` utilisateur⋅rice.
Tous les utilisateurs peuvent être membre de ``Club``. Cela se traduit par une adhésion ``Membership``, dont les Toustes les utilisateur⋅rices peuvent être membre de ``Club``. Cela se traduit par une adhésion ``Membership``, dont les
caractéristiques sont propres à chaque club. caractéristiques sont propres à chaque club.
En pratique, la NoteKfet possède au minimum deux Club: **Bde** et **Kfet** (instanciés via les fixtures). Et tous En pratique, la Note Kfet possède au minimum deux clubs : **Bde** et **Kfet** (instanciés
les personnes à jour de cotisation sont membre à minima de Bde. via les fixtures). Et toutes les personnes à jour de cotisation sont membre à minima de
Être adhérent du club Kfet permet d'utiliser sa note pour consommer. BDE. Être adhérent⋅e du club Kfet permet d'utiliser sa note pour consommer.
Modèles Modèles
------- -------
Utilisateur Utilisateur⋅rice
~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
Le modèle ``User`` est directement implémenté dans Django et n'appartient pas à l'application ``member``, mais il est Le modèle ``User`` est directement implémenté dans Django et n'appartient pas à l'application ``member``, mais il est
bon de rappeler à quoi ressemble ce modèle. bon de rappeler à quoi ressemble ce modèle.
* ``date_joined`` : ``DateTimeField``, date à laquelle l'utilisateur a été inscrit (*inutilisé dans la Note*) * ``date_joined`` : ``DateTimeField``, date à laquelle l'utilisateur⋅rice a été inscrit (*inutilisé dans la Note*)
* ``email`` : ``EmailField``, adresse e-mail de l'utilisateur. * ``email`` : ``EmailField``, adresse e-mail de l'utilisateur⋅rice.
* ``first_name`` : ``CharField``, prénom de l'utilisateur. * ``first_name`` : ``CharField``, prénom de l'utilisateur⋅rice.
* ``is_active`` : ``BooleanField``, indique si le compte est actif et peut se connecter. * ``is_active`` : ``BooleanField``, indique si le compte est actif et peut se connecter.
* ``is_staff`` : ``BooleanField``, indique si l'utilisateur peut se connecter à l'interface Django-admin. * ``is_staff`` : ``BooleanField``, indique si l'utilisateur⋅rice peut se connecter à l'interface Django-admin.
* ``is_superuser`` : ``BooleanField``, indique si l'utilisateur dispose de droits super-utilisateurs, permettant n'importe quelle action en base de donnée (lecture, ajout, modification, suppression). * ``is_superuser`` : ``BooleanField``, indique si l'utilisateur⋅rice dispose de droits super-utilisateur⋅rices, permettant n'importe quelle action en base de donnée (lecture, ajout, modification, suppression).
* ``last_login`` : ``DateTimeField``, date et heure de dernière connexion. * ``last_login`` : ``DateTimeField``, date et heure de dernière connexion.
* ``last_name`` : ``CharField``, nom de famille de l'utilisateur. * ``last_name`` : ``CharField``, nom de famille de l'utilisateur⋅rice.
* ``password`` : ``CharField``, contient le hash du mot de passe de l'utilisateur. L'algorithme utilisé est celui par défaut de Django : PBKDF2 + HMAC + SHA256 avec 150000 itérations. * ``password`` : ``CharField``, contient le hash du mot de passe de l'utilisateur⋅rice. L'algorithme utilisé est celui par défaut de Django : PBKDF2 + HMAC + SHA256 avec 150000 itérations.
* ``username`` : ``CharField`` (unique), pseudo de l'utilisateur. * ``username`` : ``CharField`` (unique), pseudo de l'utilisateur⋅rice.
Profil Profil
~~~~~~ ~~~~~~
Le modèle ``Profile`` contient un champ ``user`` de type ``OneToOneField``, ce qui permet de voir ce modèle comme une Le modèle ``Profile`` contient un champ ``user`` de type ``OneToOneField``, ce qui permet de voir ce modèle comme une
extension du modèle ``User``, sans avoir à le réécrire. Il contient diverses informations personnelles sur extension du modèle ``User``, sans avoir à le réécrire. Il contient diverses informations personnelles sur
l'utilisateur, utiles pour l'adhésion au BDE : l'utilisateur⋅rice, utiles pour l'adhésion au BDE :
* ``user`` : ``OneToOneField(User)``, utilisateur lié à ce profil * ``user`` : ``OneToOneField(User)``, utilisateur⋅rice lié à ce profil
* ``address`` : ``CharField``, adresse physique de l'utilisateur * ``address`` : ``CharField``, adresse physique de l'utilisateur⋅rice
* ``paid`` : ``BooleanField``, indique si l'utilisateur normalien est rémunéré ou non (utile pour différencier les montants d'adhésion aux clubs) * ``paid`` : ``BooleanField``, indique si l'utilisateur⋅rice normalien⋅ne est rémunéré⋅e ou non (utile pour différencier les montants d'adhésion aux clubs)
* ``phone_number`` : ``CharField``, numéro de téléphone de l'utilisateur * ``phone_number`` : ``CharField``, numéro de téléphone de l'utilisateur⋅rice
* ``section`` : ``CharField``, section de l'ENS à laquelle apartient l'utilisateur (exemple : 1A0, ...) * ``section`` : ``CharField``, section de l'ENS à laquelle appartient l'utilisateur⋅rice (exemple : 1A0,)
Clubs Clubs
~~~~~ ~~~~~
La gestion des clubs est une différence majeure avec la Note Kfet 2015. La Note gère ainsi les adhésions des La gestion des clubs est une différence majeure avec la Note Kfet 2015. La Note gère ainsi les adhésions des
utilisateurs aux différents clubs. utilisateur⋅rices aux différents clubs.
* ``parent_club`` : ``ForeignKey(Club)``. La présence d'un club parent force l'adhésion au club parent avant de pouvoir adhérer au dit club. Tout club qui n'est pas le club BDE doit avoir le club BDE dans son arborescence. * ``parent_club`` : ``ForeignKey(Club)``. La présence d'un club parent force l'adhésion au club parent avant de pouvoir adhérer au dit club. Tout club qui n'est pas le club BDE doit avoir le club BDE dans son arborescence.
* ``email`` : ``EmailField``, adresse e-mail sur laquelle contacter le bureau du club. * ``email`` : ``EmailField``, adresse e-mail sur laquelle contacter le bureau du club.
* ``membership_start`` : ``DateField``, date à partir de laquelle il est possible d'adhérer à un club pour l'année suivante (si adhésions à l'année), en ignorant l'année. Par exemple, l'adhésion BDE est possible à partir du 31/08 par défaut, et c'est à cette date que les adhésions pour l'année future est possible. * ``membership_start`` : ``DateField``, date à partir de laquelle il est possible d'adhérer à un club pour l'année suivante (si adhésions à l'année), en ignorant l'année. Par exemple, l'adhésion BDE est possible à partir du 01/08 par défaut, et c'est à cette date que les adhésions pour l'année future est possible.
* ``membership_end`` : ``DateField``, date maximale de fin d'adhésion. Pour le club BDE, il s'agit du 30/09 de l'année suivante. Si cette valeur vaut ``null``, la fin d'adhésion n'est pas limitée. * ``membership_end`` : ``DateField``, date maximale de fin d'adhésion. Pour le club BDE, il s'agit du 30/09 de l'année suivante. Si cette valeur vaut ``null``, la fin d'adhésion n'est pas limitée.
* ``membership_duration`` : ``PositiveIntegerField``, durée (en jours) maximale d'adhésion. Par exemple, le club BDE permet des adhésions maximales de 13 mois, soit 396 jours. * ``membership_duration`` : ``PositiveIntegerField``, durée (en jours) maximale d'adhésion. Par exemple, le club BDE permet des adhésions maximales de 13 mois, soit 396 jours.
* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un élève normalien (donc rémunéré) puisse adhérer. * ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e élève normalien⋅ne (donc rémunéré⋅e) puisse adhérer.
* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un étudiant normalien (donc non rémunéré) puisse adhérer. * ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e étudiant⋅e normalien⋅ne (donc non rémunéré) puisse adhérer.
* ``name`` : ``CharField``, nom du club. * ``name`` : ``CharField``, nom du club.
* ``require_memberships`` : ``BooleanField``, indique si le club est un vrai club BDE qui nécessite des adhésions de club, ou s'il s'agit d'une note "pot commun" (organisation d'une activité, note de département, ...) * ``require_memberships`` : ``BooleanField``, indique si le club est un vrai club BDE qui nécessite des adhésions de club, ou s'il s'agit d'une note "pot commun" (organisation d'une activité, note de département,)
Adhésions Adhésions
~~~~~~~~~ ~~~~~~~~~
@ -67,16 +67,16 @@ Adhésions
Comme indiqué précédemment, la note gère les adhésions. Comme indiqué précédemment, la note gère les adhésions.
* ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion. * ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion.
* ``user`` : ``ForeignKey(User)``, utilisateur adhéré. * ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui a adhéré.
* ``date_start`` : ``DateField``, date de début d'adhésion. * ``date_start`` : ``DateField``, date de début d'adhésion.
* ``date_end`` : ``DateField``, date de fin d'adhésion. * ``date_end`` : ``DateField``, date de fin d'adhésion.
* ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée. * ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée.
* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent. * ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent⋅e.
Rôles Rôles
~~~~~ ~~~~~
Comme indiqué le modèle des adhésions, les adhésions octroient des rôles aux adhérents, qui offrent des permissions Comme indiqué le modèle des adhésions, les adhésions octroient des rôles aux adhérent⋅es, qui offrent des permissions
(cf ``RolesPermissions`` dans la page des permissions). Le modèle ``RolesPermissions`` possède un (cf ``RolesPermissions`` dans la page des permissions). Le modèle ``RolesPermissions`` possède un
``OneToOneField(Role)``, qui implémente les permissions des rôles. Le modèle ``Role`` à proprement parler ne contient ``OneToOneField(Role)``, qui implémente les permissions des rôles. Le modèle ``Role`` à proprement parler ne contient
que le champ de son nom (``CharField``). que le champ de son nom (``CharField``).
@ -88,7 +88,7 @@ Si le modèle ``MembershipTransaction`` appartient à l'application ``note``, il
Le modèle ``MembershipTransaction`` est une extension du modèle ``Transaction`` (application ``note``) qui est de type Le modèle ``MembershipTransaction`` est une extension du modèle ``Transaction`` (application ``note``) qui est de type
polymorphique, et contient en plus des informations de base de la transaction un champ ``OneToOneField(Membership)`` polymorphique, et contient en plus des informations de base de la transaction un champ ``OneToOneField(Membership)``
faisant le lien entre l'adhésion et la transaction liée. Une adhésion club, si elle n'est pas gratuite, faisant le lien entre l'adhésion et la transaction liée. Une adhésion club, si elle n'est pas gratuite,
génère en effet automatiquement une transaction de l'utilisateur vers le club (voir section adhésions). génère en effet automatiquement une transaction de l'utilisateur⋅rice vers le club (voir section adhésions).
Graphe Graphe
------ ------
@ -100,28 +100,28 @@ Adhésions
--------- ---------
La Note Kfet offre la possibilité aux clubs de gérer l'adhésion de leurs membres. En plus de réguler les cotisations La Note Kfet offre la possibilité aux clubs de gérer l'adhésion de leurs membres. En plus de réguler les cotisations
des adhérents, des permissions sont octroyées sur la note en fonction des rôles au sein des clubs. Un rôle est une des adhérent⋅es, des permissions sont octroyées sur la note en fonction des rôles au sein des clubs. Un rôle est une
fonction occupée au sein d'un club (Trésorier de club, président de club, GCKfet, Res[pot], respo info, ...). fonction occupée au sein d'un club (Trésorièr⋅e de club, président⋅e de club, GC Kfet, Res[pot], respo info,).
Une adhésion attribue à un adhérent ses rôles. Les rôles fournissent les permissions. Par exemple, le trésorier d'un Une adhésion attribue à un⋅e adhérent⋅e ses rôles. Les rôles fournissent les permissions. Par exemple, læ trésorièr⋅e d'un
club a le droit de faire des transferts de et vers la note du club, tant que la source reste au-dessus de -50 €. club a le droit de faire des transferts de et vers la note du club, tant que la source reste au-dessus de -50 €.
Une adhésion est considérée comme valide si la date du jour est comprise (au sens large) entre les dates de début et Une adhésion est considérée comme valide si la date du jour est comprise (au sens large) entre les dates de début et
de fin d'adhésion. de fin d'adhésion.
On peut ajouter une adhésion à un utilisateur dans un club à tout non adhérent de ce club. La personne en charge On peut ajouter une adhésion à un⋅e utilisateur⋅rice dans un club à tout⋅e non adhérent⋅e de ce club. La personne en charge
d'adhérer quelqu'un choisit l'utilisateur, les rôles au sein du club et la date de début d'adhésion. Cette date de d'adhérer quelqu'un choisit l'utilisateur⋅rice, les rôles au sein du club et la date de début d'adhésion. Cette date de
début d'adhésion doit se situer entre les champs ``club.membership_start`` et ``club.membership_end``, début d'adhésion doit se situer entre les champs ``club.membership_start`` et ``club.membership_end``,
si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club. si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur⋅rice doit être membre de ce club.
Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club.membership_fee_paid`` Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur⋅rice (``club.membership_fee_paid``
centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiant⋅es). La date de fin est calculée
comme ce qui suit : comme ce qui suit :
* Si ``club.membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration`` * Si ``club.membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration``
* Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie) * Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie)
* Si ``club.membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club.membership_end``) * Si ``club.membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club.membership_end``)
Si l'utilisateur n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est Si l'utilisateur⋅rice n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est
insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type
``MembershipTransaction`` est automatiquement créée de la note de l'utilisateur vers la note du club, finalisant l'adhésion. ``MembershipTransaction`` est automatiquement créée de la note de l'utilisateur⋅rice vers la note du club, finalisant l'adhésion.
Réadhésions Réadhésions
~~~~~~~~~~~ ~~~~~~~~~~~
@ -137,7 +137,7 @@ Il est possible de réadhérer si :
* Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership.date_start`` >= ``club.membership_start``) * Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership.date_start`` >= ``club.membership_start``)
Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées. Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées.
En réadhérant, une nouvelle adhésion est créée pour l'utilisateur avec les mêmes rôles, commençant le lendemain de la En réadhérant, une nouvelle adhésion est créée pour l'utilisateur⋅rice avec les mêmes rôles, commençant le lendemain de la
date d'expiration de la précédente adhésion. Si on réadhère le 16 août pour une adhésion finissant le 30 septembre, date d'expiration de la précédente adhésion. Si on réadhère le 16 août pour une adhésion finissant le 30 septembre,
la nouvelle adhésion commencera le 1er octobre). la nouvelle adhésion commencera le 1er octobre).

View File

@ -7,23 +7,23 @@ Affichage
La page de consommations est principalement une communication entre l'`API <../api>`_ et la page en JavaScript. La page de consommations est principalement une communication entre l'`API <../api>`_ et la page en JavaScript.
Elle est disponible à l'adresse ``/note/consos/``, et l'onglet n'est visible que pour ceux ayant le droit de voir au Elle est disponible à l'adresse ``/note/consos/``, et l'onglet n'est visible que pour ceux ayant le droit de voir au
moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4. moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4.
Les boutons que l'utilisateur a le droit de voir sont triés par catégorie. Les boutons que l'utilisateur⋅rice a le droit de voir sont triés par catégorie.
Sélection des consommations Sélection des consommations
--------------------------- ---------------------------
Lorsque l'utilisateur commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait, Lorsque l'utilisateur⋅rice commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait,
récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur survole un alias, un appel à la page récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur⋅rice survole un alias, un appel à la page
``/api/note/note/<NOTE_ID>/`` est fait pour récupérer plus d'infos sur la note telles que le solde, le vrai nom de la ``/api/note/note/<NOTE_ID>/`` est fait pour récupérer plus d'infos sur la note telles que le solde, le vrai nom de la
note et la photo, si toutefois l'utilisateur a le droit de voir ceci. note et la photo, si toutefois l'utilisateur⋅rice a le droit de voir ceci.
L'utilisateur peut cliquer sur des aliases pour ajouter des émetteurs, et sur des boutons pour ajouter des consommations. L'utilisateur⋅rice peut cliquer sur des aliases pour ajouter des émetteur⋅rices, et sur des boutons pour ajouter des consommations.
Cliquer dans la liste des émetteurs supprime l'élément sélectionné. Cliquer dans la liste des émetteur⋅rices supprime l'élément sélectionné.
Il ya deux possibilités pour faire consommer des adhérents : Il ya deux possibilités pour faire consommer des adhérent⋅es :
- En mode **consommation simple** (mode par défaut), les consommations sont débitées dès que émetteurs et consommations - En mode **consommation simple** (mode par défaut), les consommations sont débitées dès que émetteur⋅rices et consommations
sont renseignées. sont renseignées.
- En mode **consommation double**, l'utilisateur doit cliquer sur "Consommer !" pour débiter toutes les consommations. - En mode **consommation double**, l'utilisateur⋅rice doit cliquer sur « **Consommer !** »" pour débiter toutes les consommations.
Débit des consommations Débit des consommations
----------------------- -----------------------
@ -71,7 +71,7 @@ des types. Il vaut `42` lors de la rédaction de cette documentation, mais pourr
Si une erreur survient lors de la requête (droits insuffisants), un message apparaîtra en haut de page. Si une erreur survient lors de la requête (droits insuffisants), un message apparaîtra en haut de page.
Dans tous les cas, tous les champs sont réinitialisés. Dans tous les cas, tous les champs sont réinitialisés.
L'historique et la balance de l'utilisateur sont ensuite mis à jour via jQuery, qui permet de recharger une partie de page Web. L'historique et le solde de l'utilisateur⋅rice sont ensuite mis à jour via jQuery, qui permet de recharger une partie de page Web.
Validation/dévalidation des transactions Validation/dévalidation des transactions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -86,4 +86,4 @@ une requête PATCH est faite à l'API sur l'adresse ``/api/note/transaction/tran
"valid": false "valid": false
} }
L'historique et la balance sont ensuite rafraîchis. Si une erreur survient, un message apparaîtra. L'historique et le solde sont ensuite rafraîchis. Si une erreur survient, un message apparaîtra.

View File

@ -1,12 +1,12 @@
Application Note Application Note
================ ================
L'application ``note`` gère tout ce qui est en lien avec les flux d'argent et les notes (balances) des utilisateurs. L'application ``note`` gère tout ce qui est en lien avec les flux d'argent et les notes (soldes) des utilisateur⋅rices.
La gestion des consommations s'effectue principalement via la page dédiée, dont le fonctionnement est expliqué La gestion des consommations s'effectue principalement via la page dédiée, dont le fonctionnement est expliqué
dans la page `Consommations <consumptions>`_. dans la page `Consommations <consumptions>`_.
Le fonctionnnemnent des crédit/débit de note (avec le "monde extérieur" donc avec de l'argent réel) ainsi que les Le fonctionnement des crédit/débit de note (avec le « monde extérieur »» donc avec de l'argent réel) ainsi que les
transferts/dons entre notes est détaillé sur la page `Transferts <transactions>`_. transferts/dons entre notes est détaillé sur la page `Transferts <transactions>`_.
.. toctree:: .. toctree::

View File

@ -6,7 +6,7 @@ Affichage
L'interface de la page de transferts est semblable à celles des consommations, et l'auto-complétion de note est géré de L'interface de la page de transferts est semblable à celles des consommations, et l'auto-complétion de note est géré de
la même manière. La page se trouve à l'adresse ``/note/transfer/``. La liste des 20 transactions les plus récentes que la même manière. La page se trouve à l'adresse ``/note/transfer/``. La liste des 20 transactions les plus récentes que
l'utilisateur a le droit de voir est également présente. l'utilisateur⋅rice a le droit de voir est également présente.
Des boutons ``Don``, ``Transfert``, ``Crédit``, ``Retrait`` sont présents, représentant les différents modes de Des boutons ``Don``, ``Transfert``, ``Crédit``, ``Retrait`` sont présents, représentant les différents modes de
transfert. Pour chaque transfert, un montant et une description sont attendus. transfert. Pour chaque transfert, un montant et une description sont attendus.
@ -23,7 +23,7 @@ Onglets Crédit et retrait
Ces onglets ne sont visibles que par ceux qui ont le droit de voir les ``SpecialNote``. Ces onglets ne sont visibles que par ceux qui ont le droit de voir les ``SpecialNote``.
Une boîte supplémentaire apparaît, demandant en plus de la note, du montant et de la raison le nom, le prénom et Une boîte supplémentaire apparaît, demandant en plus de la note, du montant et de la raison le nom, le prénom et
la banque de la personne à recharger/retirer. Lorsqu'une note est sélectionnée, les champs "nom" et "prénom" sont la banque de la personne à recharger/retirer. Lorsqu'une note est sélectionnée, les champs « nom » et « prénom » sont
remplis automatiquement. Par ailleurs, seule une note peut être choisie. remplis automatiquement. Par ailleurs, seule une note peut être choisie.
Transfert Transfert

View File

@ -1,8 +1,8 @@
Droits Droits
====== ======
Le système de droit par défault de django n'est pas suffisament granulaire pour les besoins de la NoteKfet2020. Le système de droit par défaut de Django n'est pas suffisamment granulaire pour les besoins de la Note Kfet 2020.
Un système custom a donc été développé. Un système personnalisé a donc été développé.
Il permet la création de Permission, qui autorise ou non a faire une action précise sur un ou des objets Il permet la création de Permission, qui autorise ou non a faire une action précise sur un ou des objets
de la base de données. de la base de données.
@ -22,12 +22,12 @@ Une permission est un Model Django dont les principaux attributs sont :
* ``query`` : Requête sur la cible, encodé en JSON, traduit en un Q object (cf `Query <#compilation-de-la-query>`_) * ``query`` : Requête sur la cible, encodé en JSON, traduit en un Q object (cf `Query <#compilation-de-la-query>`_)
* ``field`` : le champ cible qui pourra être modifié. (tous les champs si vide) * ``field`` : le champ cible qui pourra être modifié. (tous les champs si vide)
Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre Pour savoir si un⋅e utilisateur⋅rice a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre
de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprete comme tout ce qui suit de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprète comme tout ce qui suit
un ``WHERE ...`` Ils peuvent être combiné à l'aide d'opérateurs logiques. Plus d'information sur les Q object dans la un ``WHERE ...`` Ils peuvent être combiné à l'aide d'opérateurs logiques. Plus d'information sur les Q object dans la
`documentation officielle <https://docs.djangoproject.com/fr/2.2/topics/db/queries/#complex-lookups-with-q-objects>`_. `documentation officielle <https://docs.djangoproject.com/fr/2.2/topics/db/queries/#complex-lookups-with-q-objects>`_.
Ce Q object sera donc utilisé pour savoir si l'instance que l'on veux modifier est concernée par notre permission. Ce Q object sera donc utilisé pour savoir si l'instance que l'on veut modifier est concernée par notre permission.
Exception faite sur l'ajout d'objets : l'objet n'existant pas encore en base de données, il est ajouté puis supprimé Exception faite sur l'ajout d'objets : l'objet n'existant pas encore en base de données, il est ajouté puis supprimé
à la volée, en prenant soin de désactiver les signaux. à la volée, en prenant soin de désactiver les signaux.
@ -36,7 +36,7 @@ Compilation de la query
----------------------- -----------------------
La query est enregistrée sous un format JSON, puis est traduite en requête ``Q`` récursivement en appliquant certains paramètres. La query est enregistrée sous un format JSON, puis est traduite en requête ``Q`` récursivement en appliquant certains paramètres.
Le fonctionnemente de base des permission peux être décris avec les differents opérations : Le fonctionnemente de base des permission peux être décris avec les différents opérations :
+----------------+-----------------------------+-------------------------------------+ +----------------+-----------------------------+-------------------------------------+
| opérations | JSON | Q object | | opérations | JSON | Q object |
@ -64,7 +64,7 @@ Exemples
{"is_superuser": true} {"is_superuser": true}
| si l'utilisateur cible est un super utilisateur. | si l'utilisateur⋅rice cible est un⋅e super utilisateur⋅rice.
* sur le model ``Note`` : * sur le model ``Note`` :
@ -74,7 +74,7 @@ Exemples
["user","note", "pk"] ["user","note", "pk"]
} }
| si l'identifiant de la note cible est l'identifiant de l'utilisateur dont on regarde la permission. | si l'identifiant de la note cible est l'identifiant de l'utilisateur⋅rice dont on regarde la permission.
* sur le model ``Transaction``: * sur le model ``Transaction``:
@ -87,7 +87,7 @@ Exemples
["user", "note", "balance"]} ["user", "note", "balance"]}
] ]
| si la source est la note de l'utilisateur et si le montant est inférieur à son solde. | si la source est la note de l'utilisateur⋅rice et si le montant est inférieur à son solde.
* Sur le model ``Alias`` * Sur le model ``Alias``
@ -106,7 +106,7 @@ Exemples
} }
] ]
| si l'alias appartient à une note de club ou s'il appartient à la note d'un utilisateur membre du club Kfet. | si l'alias appartient à une note de club ou s'il appartient à la note d'un⋅e utilisateur⋅rice membre du club Kfet.
* sur le model ``Transaction`` * sur le model ``Transaction``
@ -130,19 +130,19 @@ Exemples
Masques de permissions Masques de permissions
---------------------- ----------------------
Chaque permission est associée à un masque. À la connexion, l'utilisateur choisit le masque de droits avec lequel il Chaque permission est associée à un masque. À la connexion, l'utilisateur⋅rice choisit le masque de droits avec lequel il
souhaite se connecter. Les masques sont ordonnés totalement, et l'utilisateur aura effectivement une permission s'il est souhaite se connecter. Les masques sont ordonnés totalement, et l'utilisateur⋅rice aura effectivement une permission s'il est
en droit d'avoir la permission et si son masque est suffisamment haut. en droit d'avoir la permission et si son masque est suffisamment haut.
Par exemple, si la permission de voir toutes les transactions est associée au masque "Droits note uniquement", Par exemple, si la permission de voir toutes les transactions est associée au masque « Droits note uniquement »,
se connecter avec le masque "Droits basiques" n'octroiera pas cette permission tandis que le masque "Tous mes droits" oui. se connecter avec le masque « Droits basiques » n'octroiera pas cette permission tandis que le masque « Tous mes droits » oui.
Signaux Signaux
------- -------
À chaque fois qu'un modèle est modifié, ajouté ou supprimé, les droits sont contrôlés. Si les droits ne sont pas À chaque fois qu'un modèle est modifié, ajouté ou supprimé, les droits sont contrôlés. Si les droits ne sont pas
suffisants, une erreur est lancée. Pour ce qui est de la modification, on ne contrôle que les champs réellement suffisants, une erreur est lancée. Pour ce qui est de la modification, on ne contrôle que les champs réellement
modifiés en comparant l'ancienne et la nouvele instance. modifiés en comparant l'ancienne et la nouvelle instance.
Graphe des modèles Graphe des modèles
------------------ ------------------

View File

@ -4,7 +4,7 @@ Inscriptions
L'inscription a la note se fait via une application dédiée, sans toutefois avoir de modèle en base de données. L'inscription a la note se fait via une application dédiée, sans toutefois avoir de modèle en base de données.
Un formulaire d'inscription est disponible sur la page ``/registration/signup``, accessible depuis n'importe qui, Un formulaire d'inscription est disponible sur la page ``/registration/signup``, accessible depuis n'importe qui,
authentifié ou non. Les informations suivantes sont demandées : authentifié⋅e ou non. Les informations suivantes sont demandées :
* Prénom * Prénom
* Nom de famille * Nom de famille
@ -15,7 +15,7 @@ authentifié ou non. Les informations suivantes sont demandées :
* Département d'études * Département d'études
* Promotion, année d'entrée à l'ENS * Promotion, année d'entrée à l'ENS
* Adresse (optionnel) * Adresse (optionnel)
* Payé (si la personne perçoit un salaire) * Payé⋅e (si la personne perçoit un salaire)
Le mot de passe doit vérifier des contraintes de longueur, de complexité et d'éloignement des autres informations Le mot de passe doit vérifier des contraintes de longueur, de complexité et d'éloignement des autres informations
personnelles. personnelles.
@ -34,28 +34,28 @@ le compte sera enfin actif.
Pour récapituler : compte actif = adresse e-mail validée + inscription validée par le BDE. Pour récapituler : compte actif = adresse e-mail validée + inscription validée par le BDE.
Lors de la validation de l'inscription, le BDE peut (et doit même) faire un crédit initial sur la future note de Lors de la validation de l'inscription, le BDE peut (et doit même) faire un crédit initial sur la future note de
l'utilisateur. Il peut spécifier le type de crédit (carte bancaire/espèces/chèque/virement bancaire), le prénom, l'utilisateur⋅rice. Il peut spécifier le type de crédit (carte bancaire/espèces/chèque/virement bancaire), le prénom,
le nom et la banque comme un crédit normal. Cependant, il peut aussi cocher une case "Société générale", si le nouveau le nom et la banque comme un crédit normal. Cependant, il peut aussi cocher une case "Société générale", si le nouveau
membre indique avoir ouvert un compte à la Société générale via le partenariat Société générale - BDE de membre indique avoir ouvert un compte à la Société générale via le partenariat Société générale - BDE de
l'ÉNS Paris-Saclay. Dans ce cas, tous les champs sont grisés. l'ÉNS Paris-Saclay. Dans ce cas, tous les champs sont grisés.
Une fois l'inscription validée, détail de ce qu'il se passe : Une fois l'inscription validée, détail de ce qu'il se passe :
* Si crédit de la socitété générale, on mémorise que le fait que la personne ait demandé ce crédit (voir * Si crédit de la société générale, on mémorise que le fait que la personne ait demandé ce crédit (voir
`Trésorerie <treasury>`_ section crédits de la société générale). Nécessairement, le club Kfet doit être rejoint. `Trésorerie <treasury>`_ section crédits de la société générale). Nécessairement, le club Kfet doit être rejoint.
* Sinon, on crédite la note du montant demandé par le nouveau membre (avec comme description "Crédit TYPE (Inscription)" * Sinon, on crédite la note du montant demandé par læ nouvelleau membre (avec comme description "Crédit TYPE (Inscription)"
où TYPE est le type de crédit), après avoir vérifié que le crédit est suffisant (on n'ouvre pas une note négative) où TYPE est le type de crédit), après avoir vérifié que le crédit est suffisant (on n'ouvre pas une note négative)
* On adhère la personne au BDE, l'adhésion commence aujourd'hui. Il dispose d'un unique rôle : "Adhérent BDE", * On adhère la personne au BDE, l'adhésion commence aujourd'hui. Iel dispose d'un unique rôle : « Adhérent⋅e BDE »,
lui octroyant un faible nombre de permissions de base, telles que la visualisation de son compte. lui octroyant un faible nombre de permissions de base, telles que la visualisation de son compte.
* On adhère la personne au club Kfet si cela est demandé, l'adhésion commence aujourd'hui. Il dispose d'un unique rôle : * On adhère la personne au club Kfet si cela est demandé, l'adhésion commence aujourd'hui. Iel dispose d'un unique rôle :
"Adhérent Kfet", lui octroyant un nombre un peu plus conséquent de permissions basiques, telles que la possibilité de « Adhérent⋅e Kfet» , lui octroyant un nombre un peu plus conséquent de permissions basiques, telles que la possibilité de
faire des transactions, d'accéder aux activités, au WEI, ... faire des transactions, d'accéder aux activités, au WEI,
* Si le nouveau membre a indiqué avoir ouvert un compte à la société générale, alors les transactions sont invalidées, * Si læ nouvelleau membre a indiqué avoir ouvert un compte à la société générale, alors les transactions sont invalidées,
la note n'est pas débitée (commence alors à 0 €). la note n'est pas débitée (commence alors à 0 €).
Par ailleurs, le BDE peut supprimer la demande d'inscription sans problème via un bouton dédié. Cette opération Par ailleurs, le BDE peut supprimer la demande d'inscription sans problème via un bouton dédié. Cette opération
n'est pas réversible. n'est pas réversible.
L'utilisateur a enfin accès a sa note et peut faire des bêtises :) L'utilisateur⋅rice a enfin accès a sa note et peut faire des bêtises :)
L'inscription au BDE et à la Kfet est indépendante de l'inscription au WEI. Voir `WEI <wei>`_ pour l'inscription WEI. L'inscription au BDE et à la Kfet est indépendante de l'inscription au WEI. Voir `WEI <wei>`_ pour l'inscription WEI.

View File

@ -1,7 +1,7 @@
Application Trésorerie Application Trésorerie
====================== ======================
L'application de Trésorerie facilite la vie des trésorier, et sert d'interface de création de facture. L'application de Trésorerie facilite la vie des trésorièr⋅es, et sert d'interface de création de facture.
Elle permet également le suivi des remises de chèques reçus par le BDE et des crédits de la Société générale. Elle permet également le suivi des remises de chèques reçus par le BDE et des crédits de la Société générale.
Factures Factures
@ -33,7 +33,7 @@ Produits
* ``invoice`` : ``ForeignKey`` vers la facture associée au produit * ``invoice`` : ``ForeignKey`` vers la facture associée au produit
* ``designation`` : Désignation du produit * ``designation`` : Désignation du produit
* ``quantity`` : Quantité achetée * ``quantity`` : Quantité achetée
* ``amount`` : Prix unitaire (HT) du produit (peut être négatif si jamais il s'agit d'un rabais, d'un solde prépayé, ...) * ``amount`` : Prix unitaire (HT) du produit (peut être négatif si jamais il s'agit d'un rabais, d'un solde prépayé,)
Pour ajouter des produits à une facture, cela se passe sur le même formulaire d'ajout/de modification de factures. Pour ajouter des produits à une facture, cela se passe sur le même formulaire d'ajout/de modification de factures.
Pour cela, on utilise un ``FormSet``, qui permet de gérer un nombre arbitraire de formulaires Pour cela, on utilise un ``FormSet``, qui permet de gérer un nombre arbitraire de formulaires
@ -90,7 +90,7 @@ présent à l'adresse suivante :
On le remplit avec les données de la facture et les données du BDE, hard-codées. On copie le template rempli dans un On le remplit avec les données de la facture et les données du BDE, hard-codées. On copie le template rempli dans un
ficher tex dans un dossier temporaire. On fait ensuite 2 appels à ``pdflatex`` pour générer la facture au format PDF. ficher tex dans un dossier temporaire. On fait ensuite 2 appels à ``pdflatex`` pour générer la facture au format PDF.
Les deux appels sont nécessaires, il y a besoin d'un double rendu. Ensuite, le PDF est envoyé à l'utilisateur et on Les deux appels sont nécessaires, il y a besoin d'un double rendu. Ensuite, le PDF est envoyé à l'utilisateur⋅rice et on
supprime les données temporaires. supprime les données temporaires.
On remarque que les PDF sont générés à la volée et ne sont pas sauvegardés. Niveau performances, cela prend du temps On remarque que les PDF sont générés à la volée et ne sont pas sauvegardés. Niveau performances, cela prend du temps
@ -155,7 +155,7 @@ Relations
~~~~~~~~~ ~~~~~~~~~
* Toute transaction qui n'est pas attachée à une remise d'un bon type peut être attachée à une remise. Cela se passe * Toute transaction qui n'est pas attachée à une remise d'un bon type peut être attachée à une remise. Cela se passe
par le biais d'un formulaire, où le trésorier peut vérifier et corriger au besoin nom, prénom, banque émettrice et montant. par le biais d'un formulaire, où læ trésorièr⋅e peut vérifier et corriger au besoin nom, prénom, banque émettrice et montant.
* Toute transaction attachée à une remise encore ouverte peut être retirée. * Toute transaction attachée à une remise encore ouverte peut être retirée.
* Pour clore une remise, il faut au moins 1 transaction associée. * Pour clore une remise, il faut au moins 1 transaction associée.
@ -174,41 +174,41 @@ Modèle
Cette sous-application dispose d'un unique modèle "SogeCredit" avec les champs suivant : Cette sous-application dispose d'un unique modèle "SogeCredit" avec les champs suivant :
* ``user`` : ``OneToOneField`` vers ``User``, utilisateur associé à ce crédit (relation ``OneToOne`` car chaque * ``user`` : ``OneToOneField`` vers ``User``, utilisateur⋅rice associé à ce crédit (relation ``OneToOne`` car chaque
utilisateur ne peut bénéficier qu'une seule fois d'un crédit de la Société générale) utilisateur⋅rice ne peut bénéficier qu'une seule fois d'un crédit de la Société générale)
* ``transactions`` : ``ManyToManyField`` vers ``MembershipTransaction``, liste des transactions d'adhésion associées * ``transactions`` : ``ManyToManyField`` vers ``MembershipTransaction``, liste des transactions d'adhésion associées
à ce crédit, généralement adhésion BDE+Kfet+WEI même si cela n'est pas restreint à ce crédit, généralement adhésion BDE+Kfet+WEI même si cela n'est pas restreint
* ``credit_transaction`` : ``OneToOneField`` vers ``SpecialTransaction``, peut être nulle, transaction de crédit de la * ``credit_transaction`` : ``OneToOneField`` vers ``SpecialTransaction``, peut être nulle, transaction de crédit de la
Société générale vers la note de l'utilisateur si celui-ci a été validé. C'est d'ailleurs le témoin Société générale vers la note de l'utilisateur⋅rice si celui-ci a été validé. C'est d'ailleurs le témoin
de validation du crédit. de validation du crédit.
On sait qu'un utilisateur a déjà demandé un crédit de la Société générale s'il existe un crédit associé à cet On sait qu'un⋅e utilisateur⋅rice a déjà demandé un crédit de la Société générale s'il existe un crédit associé à cet⋅te
utilisateur avec une transaction associée. Par ailleurs, le modèle ``Profile`` contient une propriété ``soge`` qui utilisateur⋅rice avec une transaction associée. Par ailleurs, le modèle ``Profile`` contient une propriété ``soge`` qui
traduit exactement ceci, et qui vaut ``False`` si jamais l'application Trésorerie n'est pas chargée. traduit exactement ceci, et qui vaut ``False`` si jamais l'application Trésorerie n'est pas chargée.
Si jamais l'utilisateur n'a pas encore demandé de crédit de la Société générale (ou que celui-ci n'est pas encore validé), Si jamais l'utilisateur⋅rice n'a pas encore demandé de crédit de la Société générale (ou que celui-ci n'est pas encore validé),
l'utilisateur peut demander un tel crédit lors de son adhésion BDE, de sa réadhésion BDE ou de son inscription au WEI. l'utilisateur⋅rice peut demander un tel crédit lors de son adhésion BDE, de sa réadhésion BDE ou de son inscription au WEI.
Dans les deux premiers cas, il est invité à jumeler avec une nouvelle adhésion Kfet (merci de d'abord se réadhérer au Dans les deux premiers cas, iel est invité⋅e à jumeler avec une nouvelle adhésion Kfet (merci de d'abord se réadhérer au
BDE avant la Kfet dans ce cas). BDE avant la Kfet dans ce cas).
Lorsqu'une telle demande est faite, l'adhésion est créée avec une transaction d'adhésion invalide. Cela implique que Lorsqu'une telle demande est faite, l'adhésion est créée avec une transaction d'adhésion invalide. Cela implique que
la note source n'est pas débitée et la note destination n'est pas créditée. la note source n'est pas débitée et la note destination n'est pas créditée.
Sur son interface, le trésorier peut récupérer les crédits de Société générale invalides. Deux options s'offrent à lui : Sur son interface, læ trésorièr⋅e peut récupérer les crédits de Société générale invalides. Deux options s'offrent à ellui :
* Supprimer la demande. Dans ce cas, les transactions vont être validées, la note de l'utilisateur sera débité, les * Supprimer la demande. Dans ce cas, les transactions vont être validées, la note de l'utilisateur⋅rice sera débité, les
clubs seront crédités. Puisque la demande sera supprimée, l'utilisateur pourra à nouveau à l'avenir déclarer avoir clubs seront crédités. Puisque la demande sera supprimée, l'utilisateur⋅rice pourra à nouveau à l'avenir déclarer avoir
ouvert un compte à la Société générale. Cette option est utile dans le cas où l'utilisateur est un boulet (ou pas, ouvert un compte à la Société générale. Cette option est utile dans le cas où l'utilisateur⋅rice est un boulet (ou pas,
pour d'autres raisons) et a déclaré vouloir ouvrir un compte à la Société générale sans ne rien faire. pour d'autres raisons) et a déclaré vouloir ouvrir un compte à la Société générale sans ne rien faire.
Cette action est irréversible, et n'est pas possible si la note de l'utilisateur n'a pas un solde suffisant. Cette action est irréversible, et n'est pas possible si la note de l'utilisateur⋅rice n'a pas un solde suffisant.
* Valider la demande. Dans ce cas, un crédit de la note "Virements bancaires" vers la note de l'utilisateur sera créé, * Valider la demande. Dans ce cas, un crédit de la note "Virements bancaires" vers la note de l'utilisateur⋅rice sera créé,
la transaction sera liée à la demande via le champ ``credit_note`` (et donc la demande déclarée valide), et toutes les la transaction sera liée à la demande via le champ ``credit_note`` (et donc la demande déclarée valide), et toutes les
transactions d'adhésion seront déclarées valides. transactions d'adhésion seront déclarées valides.
* Demander à un respo info s'il y a un problème pour le régler avant de faire des bêtises. Je l'admets, ça fait trois options. * Demander à un⋅e respo info s'il y a un problème pour le régler avant de faire des bêtises. Je l'admets, ça fait trois options.
La validité d'une transaction d'adhésion n'a aucune influence sur l'adhésion elle-même. Toutefois, cela se remarque rapidement ... La validité d'une transaction d'adhésion n'a aucune influence sur l'adhésion elle-même. Toutefois, cela se remarque rapidement
.. image:: /_static/img/treasury_validate_sogecredit.png .. image:: /_static/img/treasury_validate_sogecredit.png

View File

@ -23,10 +23,10 @@ Champs hérités de ``Club`` de l'application ``member`` :
* ``membership_start`` : ``DateField``, date à partir de laquelle il est possible de s'inscrire au WEI. * ``membership_start`` : ``DateField``, date à partir de laquelle il est possible de s'inscrire au WEI.
* ``membership_end`` : ``DateField``, date de fin d'adhésion possible au WEI. * ``membership_end`` : ``DateField``, date de fin d'adhésion possible au WEI.
* ``membership_duration`` : ``PositiveIntegerField``, inutilisé dans le cas d'un WEI, vaut ``None``. * ``membership_duration`` : ``PositiveIntegerField``, inutilisé dans le cas d'un WEI, vaut ``None``.
* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un élève normalien * ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e élève normalien⋅ne
(donc rémunéré) puisse adhérer. (donc rémunéré⋅e) puisse adhérer.
* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un étudiant * ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e étudiant⋅e
normalien (donc non rémunéré) puisse adhérer. normalien⋅ne (donc non rémunéré⋅e) puisse adhérer.
* ``name`` : ``CharField``, nom du WEI. * ``name`` : ``CharField``, nom du WEI.
* ``require_memberships`` : ``BooleanField``, vaut toujours ``True`` pour le WEI. * ``require_memberships`` : ``BooleanField``, vaut toujours ``True`` pour le WEI.
@ -65,27 +65,27 @@ que de dissocier les rôles propres au WEI des rôles s'appliquant pour n'import
WEIRegistration WEIRegistration
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Inscription au WEI, contenant les informations avant validation. Ce modèle est créé dès lors que quelqu'un se pré-inscrit au WEI. Inscription au WEI, contenant les informations avant validation. Ce modèle est créé dès lors que quelqu'un⋅e se pré-inscrit au WEI.
* ``user`` : ``ForeignKey(User)``, utilisateur qui s'est pré-inscrit. Ce champ est unique avec ``wei``. * ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui s'est pré-inscrit⋅e. Ce champ est unique avec ``wei``.
* ``wei`` : ``ForeignKey(WEIClub)``, le WEI auquel l'utilisateur s'est pré-inscrit. Ce champ est unique avec ``user``. * ``wei`` : ``ForeignKey(WEIClub)``, le WEI auquel l'utilisateur⋅rice s'est pré-inscrit⋅e. Ce champ est unique avec ``user``.
* ``soge_credit`` : ``BooleanField``, indique si l'utilisateur a déclaré vouloir ouvrir un compte à la Société générale. * ``soge_credit`` : ``BooleanField``, indique si l'utilisateur⋅rice a déclaré vouloir ouvrir un compte à la Société générale.
* ``caution_check`` : ``BooleanField``, indique si l'utilisateur (en 2ème année ou plus) a bien remis son chèque de * ``caution_check`` : ``BooleanField``, indique si l'utilisateur⋅rice (en 2ème année ou plus) a bien remis son chèque de
caution auprès de la trésorerie. caution auprès de la trésorerie.
* ``birth_date`` : ``DateField``, date de naissance de l'utilisateur. * ``birth_date`` : ``DateField``, date de naissance de l'utilisateur⋅rice.
* ``gender`` : ``CharField`` parmi ``male`` (Homme), ``female`` (Femme), ``non binary`` (Non binaire), genre de la personne. * ``gender`` : ``CharField`` parmi ``male`` (Homme), ``female`` (Femme), ``non binary`` (Non binaire), genre de la personne.
* ``health_issues`` : ``TextField``, problèmes de santé déclarés par l'utilisateur. * ``health_issues`` : ``TextField``, problèmes de santé déclarés par l'utilisateur⋅rice.
* ``emergency_contact_name`` : ``CharField``, nom du contact en cas d'urgence. * ``emergency_contact_name`` : ``CharField``, nom du contact en cas d'urgence.
* ``emergency_contact_phone`` : ``CharField``, numéro de téléphone du contact en cas d'urgence. * ``emergency_contact_phone`` : ``CharField``, numéro de téléphone du contact en cas d'urgence.
* ``ml_events_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des * ``ml_events_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des
événements du BDE (1A uniquement) événements du BDE (1A uniquement)
* ``ml_art_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des * ``ml_art_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des
actualités du BDA (1A uniquement) actualités du BDA (1A uniquement)
* ``ml_sport_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des * ``ml_sport_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des
actualités du BDS (1A uniquement) actualités du BDS (1A uniquement)
* ``first_year`` : ``BooleanField``, indique si l'inscription est d'un 1A ou non. Non modifiable par n'importe qui. * ``first_year`` : ``BooleanField``, indique si l'inscription est d'un⋅e 1A ou non. Non modifiable par n'importe qui.
* ``information_json`` : ``TextField`` non modifiable manuellement par n'importe qui stockant les informations du * ``information_json`` : ``TextField`` non modifiable manuellement par n'importe qui stockant les informations du
questionnaire d'inscription au WEI pour les 1A, et stocke les demandes faites par un 2A+ concerant bus, équipes et rôles. questionnaire d'inscription au WEI pour les 1A, et stocke les demandes faites par un⋅e 2A+ concernant bus, équipes et rôles.
On utilise un ``TextField`` contenant des données au format JSON pour permettre de la modularité au fil des années, On utilise un ``TextField`` contenant des données au format JSON pour permettre de la modularité au fil des années,
sans avoir à tout casser à chaque fois. sans avoir à tout casser à chaque fois.
@ -94,19 +94,19 @@ WEIMembership
Ce modèle hérite de ``Membership`` et contient les informations d'une adhésion au WEI. Ce modèle hérite de ``Membership`` et contient les informations d'une adhésion au WEI.
* ``bus`` : ``ForeignKey(Bus)``, bus dans lequel se trouve l'utilisateur. * ``bus`` : ``ForeignKey(Bus)``, bus dans lequel se trouve l'utilisateur⋅rice.
* ``team`` : ``ForeignKey(BusTeam)`` pouvant être nulle (pour les chefs de bus et électrons libres), équipe dans laquelle * ``team`` : ``ForeignKey(BusTeam)`` pouvant être nulle (pour les chefs de bus et électrons libres), équipe dans laquelle
se trouve l'utilisateur. se trouve l'utilisateur⋅rice.
* ``registration`` : ``OneToOneField(WEIRegistration)``, informations de la pré-inscription. * ``registration`` : ``OneToOneField(WEIRegistration)``, informations de la pré-inscription.
Champs hérités du modèle ``Membership`` : Champs hérités du modèle ``Membership`` :
* ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion. Doit être un ``WEIClub``. * ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion. Doit être un ``WEIClub``.
* ``user`` : ``ForeignKey(User)``, utilisateur adhéré. * ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui a adhéré.
* ``date_start`` : ``DateField``, date de début d'adhésion. * ``date_start`` : ``DateField``, date de début d'adhésion.
* ``date_end`` : ``DateField``, date de fin d'adhésion. * ``date_end`` : ``DateField``, date de fin d'adhésion.
* ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée. * ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée.
* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent. Les rôles doivent être des ``WEIRole``. * ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent⋅e. Les rôles doivent être des ``WEIRole``.
Graphe des modèles Graphe des modèles
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -123,32 +123,32 @@ Fonctionnement
Création d'un WEI Création d'un WEI
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
Seul un respo info peut créer un WEI. Pour cela, se rendre dans l'onglet WEI, puis "Liste des WEI" et enfin Seul un⋅e respo info peut créer un WEI. Pour cela, se rendre dans l'onglet WEI, puis « Liste des WEI » et enfin
"Créer un WEI". Diverses informations sont demandées, comme le nom du WEI, l'adresse mail de contact, l'année du WEI « Créer un WEI ». Diverses informations sont demandées, comme le nom du WEI, l'adresse mail de contact, l'année du WEI
(doit être unique), les dates de début et de fin, et les dates pendant lesquelles les utilisateurs peuvent s'inscrire. (doit être unique), les dates de début et de fin, et les dates pendant lesquelles les utilisateurs peuvent s'inscrire.
Don des droits à un GC WEI Don des droits à un GC WEI
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
Le GC WEI peut gérer tout ce qui a un rapport avec le WEI. Il ne peut cependant pas créer le WEI, ce privilège est Læ GC WEI peut gérer tout ce qui a un rapport avec le WEI. Iel ne peut cependant pas créer le WEI, ce privilège est
réservé au respo info. Pour avoir ses droits, le GC WEI doit s'inscrire au WEI avec le rôle GC WEI, et donc payer réservé aux respos info. Pour avoir ses droits, læ GC WEI doit s'inscrire au WEI avec le rôle GC WEI, et donc payer
en premier sa cotisation. C'est donc au respo info de créer l'adhésion du GC WEI. Voir ci-dessous pour l'inscription au WEI. en premièr⋅e sa cotisation. C'est donc aux respos info de créer l'adhésion du GC WEI. Voir ci-dessous pour l'inscription au WEI.
S'inscrire au WEI S'inscrire au WEI
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
N'importe quel utilisateur peut s'auto-inscrire au WEI, lorsque les dates d'adhésion le permettent. Ceux qui se sont N'importe quel⋅le utilisateur⋅rice peut s'auto-inscrire au WEI, lorsque les dates d'adhésion le permettent. Celleux qui se sont
déjà inscrits peuvent également inscrire un 1A. Seuls les GC WEI et les respo info peuvent inscrire un autre 2A+. déjà inscrit⋅es peuvent également inscrire un⋅e 1A. Seul⋅es les GC WEI et les respos info peuvent inscrire un⋅e autre 2A+.
À tout moment, tant que le WEI n'est pas passé, l'inscription peut être modifiée, même après validation. À tout moment, tant que le WEI n'est pas passé, l'inscription peut être modifiée, même après validation.
Inscription d'un 2A+ Inscription d'un⋅e 2A+
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Comme indiqué, les 2A+ sont assez autonomes dans leur inscription au WEI. Ils remplissent le questionnaire et sont Comme indiqué, les 2A+ sont assez autonomes dans leur inscription au WEI. Iels remplissent le questionnaire et sont
ensuite pré-inscrits. Le questionnaire se compose de plusieurs champs (voir WEIRegistration) : ensuite pré-inscrit⋅es. Le questionnaire se compose de plusieurs champs (voir WEIRegistration) :
* Est-ce que l'utilisateur a déclaré avoir ouvert un compte à la Société générale ? (Option disponible uniquemement * Est-ce que l'utilisateur⋅rice a déclaré avoir ouvert un compte à la Société générale ? (Option disponible uniquemement
si cela n'a pas été fait une année avant) si cela n'a pas été fait une année avant)
* Date de naissance * Date de naissance
* Genre (Homme/Femme/Non-binaire) * Genre (Homme/Femme/Non-binaire)
@ -159,17 +159,17 @@ ensuite pré-inscrits. Le questionnaire se compose de plusieurs champs (voir WEI
* Équipes préférées (choix multiple éventuellement vide, vide pour les chefs de bus/staff) * Équipes préférées (choix multiple éventuellement vide, vide pour les chefs de bus/staff)
* Rôles souhaités * Rôles souhaités
Les trois derniers champs n'ont aucun caractère définitif et sont simplement là en suggestion pour le GC WEI qui Les trois derniers champs n'ont aucun caractère définitif et sont simplement là en suggestion pour læ GC WEI qui
validera l'inscription. C'est utile si on hésite entre plusieurs bus. validera l'inscription. C'est utile si on hésite entre plusieurs bus.
L'inscription est ensuite créée, le GC WEI devra ensuite la valider (voir plus bas). L'inscription est ensuite créée, le GC WEI devra ensuite la valider (voir plus bas).
Inscription d'un 1A Inscription d'un⋅e 1A
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
N'importe quelle personne déjà inscrite au WEI peut inscrire un 1A. Le formulaire 1A est assez peu différent du formulaire 2A+ : N'importe quelle personne déjà inscrite au WEI peut inscrire un⋅e 1A. Le formulaire 1A est assez peu différent du formulaire 2A+ :
* Est-ce que l'utilisateur a déclaré avoir ouvert un compte à la Société générale ? * Est-ce que l'utilisateur⋅rice a déclaré avoir ouvert un compte à la Société générale ?
* Date de naissance * Date de naissance
* Genre (Homme/Femme/Non-binaire) * Genre (Homme/Femme/Non-binaire)
* Problèmes de santé * Problèmes de santé
@ -179,10 +179,10 @@ N'importe quelle personne déjà inscrite au WEI peut inscrire un 1A. Le formula
* S'inscrire à la ML BDA * S'inscrire à la ML BDA
* S'inscrire à la ML BDS * S'inscrire à la ML BDS
Le 1A ne peut donc pas choisir de son bus et de son équipe, et peut s'inscrire aux listes de diffusion. Læ 1A ne peut donc pas choisir de son bus et de son équipe, et peut s'inscrire aux listes de diffusion.
Il y a néanmoins une différence majeure : une fois le formulaire rempli, un questionnaire se lance. Il y a néanmoins une différence majeure : une fois le formulaire rempli, un questionnaire se lance.
Ce questionnaire peut varier au fil des années (voir section Questionnaire), et contient divers formulaires de collecte Ce questionnaire peut varier au fil des années (voir section Questionnaire), et contient divers formulaires de collecte
de données qui serviront à déterminer quel est le meilleur bus pour ce nouvel utilisateur. de données qui serviront à déterminer quel est le meilleur bus pour ce⋅tte nouvelleau utilisateur⋅rice.
Questionnaire 1A Questionnaire 1A
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
@ -200,7 +200,7 @@ Je veux changer d'algorithme de répartition, que faire ?
Cette section est plus technique et s'adresse surtout aux respos info en cours de mandat. Cette section est plus technique et s'adresse surtout aux respos info en cours de mandat.
Première règle : on ne supprime rien (sauf si vraiment c'est du mauvais boulot). En prenant exemple sur des fichiers déjà existant tels que ``apps/wei/forms/surveys/wei2020.py``, créer un nouveau fichier ``apps/wei/forms/surveys/wei20XY.py``. Ce fichier doit inclure les éléments suivants : Première règle : on ne supprime rien (sauf si vraiment c'est du mauvais boulot). En prenant exemple sur des fichiers déjà existant tels que ``apps/wei/forms/surveys/wei2021.py``, créer un nouveau fichier ``apps/wei/forms/surveys/wei20XY.py``. Ce fichier doit inclure les éléments suivants :
WEISurvey WEISurvey
""""""""" """""""""
@ -223,7 +223,7 @@ Une classe héritant de ``wei.forms.surveys.base.WEISurvey``, comportant les él
Naturellement, il est implicite qu'une fonction ayant pour premier argument ``cls`` doit être annotée par ``@classmethod``. Naturellement, il est implicite qu'une fonction ayant pour premier argument ``cls`` doit être annotée par ``@classmethod``.
Nativement, la classe ``WEISurvey`` comprend les informations suivantes : Nativement, la classe ``WEISurvey`` comprend les informations suivantes :
* ``registration``, le modèle ``WEIRegistration`` de l'utilisateur qui remplit le questionnaire * ``registration``, le modèle ``WEIRegistration`` de l'utilisateur⋅rice qui remplit le questionnaire
* ``information``, instance de ``WEISurveyInformation``, contient les données du questionnaire en cours de remplissage. * ``information``, instance de ``WEISurveyInformation``, contient les données du questionnaire en cours de remplissage.
* ``get_wei(cls)``, renvoie le WEI correspondant à l'année du sondage. * ``get_wei(cls)``, renvoie le WEI correspondant à l'année du sondage.
* ``save(self)``, enregistre les informations du sondage dans l'objet ``registration`` associé, qui est ensuite * ``save(self)``, enregistre les informations du sondage dans l'objet ``registration`` associé, qui est ensuite
@ -291,7 +291,7 @@ pour unique effet d'appeler la fonction ``run_algorithm`` décrite plus tôt. Un
n'a pas été évoqué d'adhésion. L'adhésion est ensuite manuelle, l'algorithme ne fournit qu'une suggestion. n'a pas été évoqué d'adhésion. L'adhésion est ensuite manuelle, l'algorithme ne fournit qu'une suggestion.
Cette structure, complexe mais raisonnable, permet de gérer plus ou moins proprement la répartition des 1A, Cette structure, complexe mais raisonnable, permet de gérer plus ou moins proprement la répartition des 1A,
en limitant très fortement le hard code. Ami nouveau développeur, merci de bien penser à la propreté du code :) en limitant très fortement le hard code. Ami nouvelleeau développeur⋅se, merci de bien penser à la propreté du code :)
En particulier, on évitera de mentionner dans le code le nom des bus, et profiter du champ ``information_json`` En particulier, on évitera de mentionner dans le code le nom des bus, et profiter du champ ``information_json``
présent dans le modèle ``Bus``. présent dans le modèle ``Bus``.
@ -300,34 +300,34 @@ Valider les inscriptions
Cette partie est moins technique. Cette partie est moins technique.
Une fois la pré-inscription faite, elle doit être validée par le BDE, afin de procéder au paiement. Le GC WEI a accès à Une fois la pré-inscription faite, elle doit être validée par le BDE, afin de procéder au paiement. Læ GC WEI a accès à
la liste des inscriptions non validées, soit sur la page de détails du WEI, soit sur un tableau plus large avec filtre. la liste des inscriptions non validées, soit sur la page de détails du WEI, soit sur un tableau plus large avec filtre.
Une inscription non validée peut soit être validée, soit supprimée (la suppression est irréversible). Une inscription non validée peut soit être validée, soit supprimée (la suppression est irréversible).
Lorsque le GC WEI veut valider une inscription, il a accès au récapitulatif de l'inscription ainsi qu'aux informations Lorsque læ GC WEI veut valider une inscription, iel a accès au récapitulatif de l'inscription ainsi qu'aux informations
personnelles de l'utilisateur. Il lui est proposé de les modifier si besoin (du moins les informations liées au WEI, personnelles de l'utilisateur⋅rice. Il lui est proposé de les modifier si besoin (du moins les informations liées au WEI,
pas les informations personnelles). Il a enfin accès aux résultats du sondage et la sortie de l'algorithme s'il s'agit pas les informations personnelles). Iel a enfin accès aux résultats du sondage et la sortie de l'algorithme s'il s'agit
d'un 1A, aux préférences d'un 2A+. Avant de valider, le GC WEI doit sélectionner un bus, éventuellement une équipe d'un⋅e 1A, aux préférences d'un⋅e 2A+. Avant de valider, læ GC WEI doit sélectionner un bus, éventuellement une équipe
et un rôle. Si c'est un 1A et que l'algorithme a tourné, ou si c'est un 2A+ qui n'a fait qu'un seul choix de bus, et un rôle. Si c'est un⋅e 1A et que l'algorithme a tourné, ou si c'est un⋅e 2A+ qui n'a fait qu'un seul choix de bus,
d'équipe, de rôles, les champs sont automatiquement pré-remplis. d'équipe, de rôles, les champs sont automatiquement pré-remplis.
Quelques restrictions cependant : Quelques restrictions cependant :
* Si c'est un 2A+, le chèque de caution doit être déclaré déposé * Si c'est un⋅e 2A+, le chèque de caution doit être déclaré déposé
* Si l'inscription se fait via la Société générale, un message expliquant la situation apparaît : la transaction de * Si l'inscription se fait via la Société générale, un message expliquant la situation apparaît : la transaction de
paiement sera créée mais invalidée, les trésoriers devront confirmer plus tard sur leur interface que le compte paiement sera créée mais invalidée, les trésorièr⋅es devront confirmer plus tard sur leur interface que le compte
à la Société générale a bien été créé avant de valider la transaction (voir `Trésorerie <treasury>`_ section à la Société générale a bien été créé avant de valider la transaction (voir `Trésorerie <treasury>`_ section
Crédit de la Société générale). Crédit de la Société générale).
* Dans le cas contraire, l'utilisateur doit avoir le solde nécessaire sur sa note avant de pouvoir adhérer. * Dans le cas contraire, l'utilisateur⋅rice doit avoir le solde nécessaire sur sa note avant de pouvoir adhérer.
* L'utilisateur doit enfin être membre du club Kfet. Un lien est présent pour le faire adhérer ou réadhérer selon le cas. * L'utilisateur⋅rice doit enfin être membre du club Kfet. Un lien est présent pour le faire adhérer ou réadhérer selon le cas.
Si tout est bon, le GC WEI peut valider. L'utilisateur a bien payé son WEI, et son interface est un peu plus grande. Si tout est bon, læ GC WEI peut valider. L'utilisateur⋅rice a bien payé son WEI, et son interface est un peu plus grande.
Il peut toujours changer ses paramètres au besoin. Un 1A ne voit rien de plus avant la fin du WEI. Iel peut toujours changer ses paramètres au besoin. Un⋅e 1A ne voit rien de plus avant la fin du WEI.
Un adhérent WEI non 1A a accès à la liste des bus, des équipes et de leur descriptions. Les chefs de bus peuvent gérer Un⋅e adhérent⋅e WEI non 1A a accès à la liste des bus, des équipes et de leur descriptions. Les chef⋅fes de bus peuvent gérer
les bus et leurs équipes. Les chefs d'équipe peuvent gérer leurs équipes. Cela inclut avoir accès à la liste des membres les bus et leurs équipes. Les chef⋅fes d'équipe peuvent gérer leurs équipes. Cela inclut avoir accès à la liste des membres
de ce bus / de cette équipe. de ce bus / de cette équipe.
Un export au format PDF de la liste des membres *visibles* est disponible pour chacun. Un export au format PDF de la liste des membres *visibles* est disponible pour chacun⋅e.
Bon WEI à tous ! Bon WEI à toustes !

View File

@ -18,7 +18,7 @@
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'Note Kfet 2020' project = 'Note Kfet 2020'
copyright = '2020-2021, BDE ENS Paris-Saclay' copyright = '2020-2022, BDE ENS Paris-Saclay'
author = 'BDE ENS Paris-Saclay' author = 'BDE ENS Paris-Saclay'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags

View File

@ -3,7 +3,7 @@ Service d'Authentification Centralisé (CAS)
Un `CAS <https://fr.wikipedia.org/wiki/Central_Authentication_Service>`_ est Un `CAS <https://fr.wikipedia.org/wiki/Central_Authentication_Service>`_ est
déployé sur la Note Kfet. Il est accessible à l'adresse `<https://note.crans.org/cas/>`_. déployé sur la Note Kfet. Il est accessible à l'adresse `<https://note.crans.org/cas/>`_.
Il a pour but uniquement d'authentifier les utilisateurs via la note et ne communique Il a pour but uniquement d'authentifier les utilisateur⋅rices via la note et ne communique
que peu d'informations. que peu d'informations.
Configuration Configuration

View File

@ -12,9 +12,9 @@ Applications externes
L'utilisation de la note par des services externes est actuellement en beta. Il est L'utilisation de la note par des services externes est actuellement en beta. Il est
fort à parier que cette utilisation sera revue et améliorée à l'avenir. fort à parier que cette utilisation sera revue et améliorée à l'avenir.
Puisque la Note Kfet recense tous les comptes des adhérents BDE, les clubs ont alors Puisque la Note Kfet recense tous les comptes des adhérent⋅es BDE, les clubs ont alors
la possibilité de développer leurs propres applications et de les interfacer avec la la possibilité de développer leurs propres applications et de les interfacer avec la
note. De cette façon, chaque application peut authentifier ses utilisateurs via la note, note. De cette façon, chaque application peut authentifier ses utilisateur⋅rices via la note,
et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts
via l'API. via l'API.
@ -25,4 +25,4 @@ Deux protocoles d'authentification sont implémentées :
À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce
mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent
ces protocoles pour récupérer leurs adhérents. ces protocoles pour récupérer leurs adhérent⋅es.

View File

@ -2,12 +2,12 @@ OAuth2
====== ======
L'authentification `OAuth2 <https://fr.wikipedia.org/wiki/OAuth>`_ est supportée par la L'authentification `OAuth2 <https://fr.wikipedia.org/wiki/OAuth>`_ est supportée par la
Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateurs, mais aussi Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateur⋅rices, mais
de transmettre des informations à un service tiers tels que des informations personnelles, aussi de transmettre des informations à un service tiers tels que des informations
le solde de la note ou encore les adhésions de l'utilisateur, en l'avertissant sur personnelles, le solde de la note ou encore les adhésions de l'utilisateur⋅rice, en
quelles données sont effectivement collectées. Ainsi, il est possible de développer des l'avertissant sur quelles données sont effectivement collectées. Ainsi, il est possible
appplications tierces qui peuvent se baser sur les données de la Note Kfet ou encore de développer des appplications tierces qui peuvent se baser sur les données de la Note
faire des transactions. Kfet ou encore faire des transactions.
Configuration du serveur Configuration du serveur
@ -79,7 +79,7 @@ Il vous suffit de donner à votre application :
* Les scopes, qui peuvent être récupérées sur cette page : `<https://note.crans.org/permission/scopes/>`_ * Les scopes, qui peuvent être récupérées sur cette page : `<https://note.crans.org/permission/scopes/>`_
* L'URL d'autorisation : `<https://note.crans.org/o/authorize/>`_ * L'URL d'autorisation : `<https://note.crans.org/o/authorize/>`_
* L'URL d'obtention de jeton : `<https://note.crans.org/o/token/>`_ * L'URL d'obtention de jeton : `<https://note.crans.org/o/token/>`_
* Si besoin, l'URL de récupération des informations de l'utilisateur : `<https://note.crans.org/api/me/>`_ * Si besoin, l'URL de récupération des informations de l'utilisateur⋅rice : `<https://note.crans.org/api/me/>`_
N'hésitez pas à consulter la page `<https://note.crans.org/api/me/>`_ pour s'imprégner N'hésitez pas à consulter la page `<https://note.crans.org/api/me/>`_ pour s'imprégner
du format renvoyé. du format renvoyé.
@ -88,14 +88,14 @@ du format renvoyé.
Un petit mot sur les scopes : tel qu'implémenté, une scope est une permission unitaire Un petit mot sur les scopes : tel qu'implémenté, une scope est une permission unitaire
(telle que décrite dans le modèle ``Permission``) associée à un club. Ainsi, un jeton (telle que décrite dans le modèle ``Permission``) associée à un club. Ainsi, un jeton
a accès à une scope si et seulement si le/la propriétaire du jeton dispose d'une adhésion a accès à une scope si et seulement si læ propriétaire du jeton dispose d'une adhésion
courante dans le club lié à la scope qui lui octroie cette permission. courante dans le club lié à la scope qui lui octroie cette permission.
Par exemple, un jeton pourra avoir accès à la permission de créer des transactions en lien Par exemple, un jeton pourra avoir accès à la permission de créer des transactions en lien
avec un club si et seulement si le propriétaire du jeton est trésorier du club. avec un club si et seulement si læ propriétaire du jeton est trésorièr⋅e du club.
La vérification des droits du propriétaire est faite systématiquement, afin de ne pas La vérification des droits de læ propriétaire est faite systématiquement, afin de ne pas
faire confiance au jeton en cas de droits révoqués à son propriétaire. faire confiance au jeton en cas de droits révoqués à saon propriétaire.
Vous pouvez donc contrôler le plus finement possible les permissions octroyées à vos Vous pouvez donc contrôler le plus finement possible les permissions octroyées à vos
jetons. jetons.
@ -118,8 +118,8 @@ du format renvoyé.
Par exemple, vous pourriez demander la permission d'accéder Par exemple, vous pourriez demander la permission d'accéder
aux membres d'un club ou de faire des transactions, et agir aux membres d'un club ou de faire des transactions, et agir
uniquement dans le cas où l'utilisateur connecté possède la uniquement dans le cas où l'utilisateur⋅rice connecté⋅e
permission problématique. possède la permission problématique.
Avec Django-allauth Avec Django-allauth
################### ###################
@ -152,7 +152,7 @@ Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut,
se connectera à ``note.crans.org`` si vous ne renseignez rien. se connectera à ``note.crans.org`` si vous ne renseignez rien.
Le paramètre ``SCOPE`` permet de définir les scopes à demander. Le paramètre ``SCOPE`` permet de définir les scopes à demander.
Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur⋅rice
et au profil sont demandées. et au profil sont demandées.
En créant l'application sur la note, vous pouvez renseigner En créant l'application sur la note, vous pouvez renseigner
@ -200,7 +200,7 @@ cas où elle n'est pas explicitement indiquée lors de l'autorisation.
Lorsqu'un client veut s'authentifier via la Note Kfet, il va devoir accéder à une page Lorsqu'un client veut s'authentifier via la Note Kfet, il va devoir accéder à une page
d'authentification. La page d'autorisation est `<https://note.crans.org/o/authorize/>`_, d'authentification. La page d'autorisation est `<https://note.crans.org/o/authorize/>`_,
c'est sur cette page qu'il faut rediriger les utilisateurs. Il faut mettre en paramètre GET : c'est sur cette page qu'il faut rediriger les utilisateur⋅rices. Il faut mettre en paramètre GET :
* ``client_id`` : l'identifiant client de l'application (public) ; * ``client_id`` : l'identifiant client de l'application (public) ;
* ``response_type`` : mettre ``code`` ; * ``response_type`` : mettre ``code`` ;
@ -211,10 +211,10 @@ c'est sur cette page qu'il faut rediriger les utilisateurs. Il faut mettre en pa
* ``state`` : optionnel, peut être utilisé pour permettre au client de détecter des requêtes * ``state`` : optionnel, peut être utilisé pour permettre au client de détecter des requêtes
provenant d'autres sites. provenant d'autres sites.
Sur cette page, les permissions demandées seront listées, et l'utilisateur aura le choix Sur cette page, les permissions demandées seront listées, et l'utilisateur⋅rice aura le
d'accepter ou non. Dans les deux cas, l'utilisateur sera redirigée vers ``redirect_uri``, choix d'accepter ou non. Dans les deux cas, l'utilisateur⋅rice sera redirigée vers
avec pour paramètre GET soit le message d'erreur, soit un paramètre ``code`` correspondant ``redirect_uri``, avec pour paramètre GET soit le message d'erreur, soit un paramètre
au code d'autorisation. ``code`` correspondant au code d'autorisation.
Une fois ce code d'autorisation récupéré, il faut désormais récupérer le jeton d'accès. Une fois ce code d'autorisation récupéré, il faut désormais récupérer le jeton d'accès.
Il faut pour cela aller sur l'URL `<https://note.crans.org/o/token/>`_, effectuer une Il faut pour cela aller sur l'URL `<https://note.crans.org/o/token/>`_, effectuer une

View File

@ -5,7 +5,7 @@ Des transactions anormales sont apparues sur mon compte.
-------------------------------------------------------- --------------------------------------------------------
.. note:: .. note::
Tu dois immédiatement contacter les trésoriers du BDE (voir ci-dessous) pour Tu dois immédiatement contacter les trésorièr⋅es du BDE (voir ci-dessous) pour
signaler l'incident. Précise bien ton nom de note, l'heure de la transaction signaler l'incident. Précise bien ton nom de note, l'heure de la transaction
ainsi que l'alias utilisé pour faire la transaction (en plaçant ta souris sur ainsi que l'alias utilisé pour faire la transaction (en plaçant ta souris sur
ton pseudo sur la ligne de transaction, l'alias utilisé apparaît). La raison ton pseudo sur la ligne de transaction, l'alias utilisé apparaît). La raison
@ -19,13 +19,13 @@ Je souhaite consommer mais le solde de ma note est insuffisant
-------------------------------------------------------------- --------------------------------------------------------------
.. note:: .. note::
Le BDE ne fait pas crédit à ses adhérents. Il est de ton devoir de t'assurer Le BDE ne fait pas crédit à ses adhérent⋅es. Il est de ton devoir de t'assurer
d'avoir en permanence un solde positif sur ta note. Les permanenciers à la d'avoir en permanence un solde positif sur ta note. Les permanencièr⋅es à la
Kfet ont la possibilité de refuser une consommation qui fait passer en négatif, Kfet ont la possibilité de refuser une consommation qui fait passer en négatif,
et ont obligation de refuser si la consommation est alcoolisée, en accord avec et ont obligation de refuser si la consommation est alcoolisée, en accord avec
la règlementation en vigueur. la règlementation en vigueur.
Les trésoriers connaissent la liste des personnes en situation irrégulière et Les trésorièr⋅es connaissent la liste des personnes en situation irrégulière et
n'hésiteront pas à faire des rappels pour recharger la note. n'hésiteront pas à faire des rappels pour recharger la note.
@ -34,9 +34,9 @@ Comment recharger ma note ?
.. note:: .. note::
Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre
de l'amicale des élèves de l'ENS Cachan, par carte bancaire via un terminal de l'amicale des élèves de l'ENS Paris-Saclay, par carte bancaire via un terminal
de paiement électronique ou encore par virement bancaire, dont les coordonnées de paiement électronique ou encore par virement bancaire, dont les coordonnées
sont à demander auprès des trésoriers BDE. sont à demander auprès des trésorièr⋅es BDE.
Les trois premières options sont à faire directement dans la Kfet. Les trois premières options sont à faire directement dans la Kfet.
@ -47,7 +47,7 @@ Je pars en stage / en vacances. Puis-je bloquer ma note ?
.. note:: .. note::
Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton
dédié. Ta note ne sera plus affichée par les autres personnes et les transferts dédié. Ta note ne sera plus affichée par les autres personnes et les transferts
seront impossibles, sauf pour les trésoriers BDE et respo info. seront impossibles, sauf pour les trésorièr⋅es BDE et respo info.
Il est toutefois de ton devoir de rembourser tout ce que tu dois. Il est toutefois de ton devoir de rembourser tout ce que tu dois.
@ -56,43 +56,44 @@ Quelle est la limite maximale au nombre d'alias d'une note ?
------------------------------------------------------------ ------------------------------------------------------------
.. note:: .. note::
Certains parlent d'une dizaine d'alias par note. Certain⋅es parlent d'une dizaine d'alias par note.
Sois conscient⋅e qu'ajouter des alias ne peut qu'augmenter la probabilité de Sois conscient⋅e qu'ajouter des alias ne peut qu'augmenter la probabilité de
collisions avec une autre note, et peut aussi retarder la livraison de ta collisions avec une autre note, et peut aussi retarder la livraison de ta
commande lors d'un perm bouffe. commande lors d'un perm bouffe.
Je suis trésorier d'un club, qu'ai-je le droit de faire ? Je suis trésorièr⋅e d'un club, qu'ai-je le droit de faire ?
--------------------------------------------------------- -----------------------------------------------------------
.. note:: .. note::
Être trésorier d'un club donne la responsabilité de gérer la trésorerie du Être trésorièr⋅e d'un club donne la responsabilité de gérer la trésorerie du
club, et donc de gérer sa note. Vous obtenez donc le droit d'effectuer club, et donc de gérer sa note. Vous obtenez donc le droit d'effectuer
n'importe quelle transaction via la note en provenance ou à destination de n'importe quelle transaction via la note en provenance ou à destination de
la note de votre club. Vous pouvez également gérer les adhésions de votre club, la note de votre club. Vous pouvez également gérer les adhésions de votre club,
en permettant à n'importe quel adhérent BDE de rejoindre votre club, en prélevant en permettant à n'importe quel⋅le adhérent⋅e BDE de rejoindre votre club, en
d'éventuels frais d'adhésion. Les paramètres du club peuvent être également modifiés. prélevant d'éventuels frais d'adhésion. Les paramètres du club peuvent être
également modifiés.
.. danger:: .. danger::
Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser. Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser.
Chaque opération nécessitant des droits doit être fait pour une bonne raison, Chaque opération nécessitant des droits doit être fait pour une bonne raison,
et doit avoir un lien avec votre club. Vous n'avez par exemple pas le droit et doit avoir un lien avec votre club. Vous n'avez par exemple pas le droit
d'aller récupérer des informations personnelles d'adhérents pour une raison d'aller récupérer des informations personnelles d'adhérent⋅es pour une raison
personnelle. En revanche, faire le lien entre nom/prénom et nom de note est personnelle. En revanche, faire le lien entre nom/prénom et nom de note est
bien sûr permis pour faciliter des transferts. Tout abus de droits constaté bien sûr permis pour faciliter des transferts. Tout abus de droits constaté
pourra mener à des sanctions prises par le bureau du BDE. pourra mener à des sanctions prises par le bureau du BDE.
.. warning:: .. warning::
Une fonctionnalité pour permettre de gérer plus proprement les remboursements Une fonctionnalité pour permettre de gérer plus proprement les remboursements
entre amis est en cours de développement. Temporairement et pour des raisons entre ami⋅es est en cours de développement. Temporairement et pour des raisons
de confort, les trésoriers de clubs ont le droit de prélever n'importe quelle de confort, les trésorièr⋅es de clubs ont le droit de prélever n'importe quelle
adhérente vers n'importe quelle autre note adhérente, tant que la source ne adhérente vers n'importe quelle autre note adhérente, tant que la source ne
descend pas sous ``- 50 €``. Ces droits seront retirés d'ici quelques semaines. descend pas sous ``- 50 €``. Ces droits seront retirés d'ici quelques semaines.
Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions Je suis trésorièr⋅e d'un club, je n'arrive pas à voir le solde du club / faire des transactions
--------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------
.. note:: .. note::
As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ? As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ?
@ -104,8 +105,8 @@ Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire d
opérations en empêchant l'accès à des opérations trop sensibles. opérations en empêchant l'accès à des opérations trop sensibles.
Je suis trésorier d'un club. Puis-je créer un bouton ? Je suis trésorièr⋅e d'un club. Puis-je créer un bouton ?
------------------------------------------------------ --------------------------------------------------------
.. note:: .. note::
Oui bien sûr ! Tant qu'il redirige bien vers la note de ton club. Oui bien sûr ! Tant qu'il redirige bien vers la note de ton club.
@ -121,20 +122,20 @@ Je suis trésorier d'un club. Puis-je créer un bouton ?
devient accessible. devient accessible.
Après passation, je suis trésorier d'un club. Comment récupérer mes droits note ? Après passation, je suis trésorièr⋅e d'un club. Comment récupérer mes droits note ?
--------------------------------------------------------------------------------- -----------------------------------------------------------------------------------
.. note:: .. note::
Tu dois pour cela contacter les trésoriers BDE (voir ci-dessous). Ils vous Tu dois pour cela contacter les trésorièr⋅es BDE (voir ci-dessous). Iels vous
expliqueront en détails vos droits et vos interdits et vous donneront les expliqueront en détails vos droits et vos interdits et vous donneront les
droits requis. droits requis.
Je souhaite contacter un trésorier Je souhaite contacter un⋅e trésorièr⋅e
---------------------------------- --------------------------------------
.. note:: .. note::
Pour contacter un trésorier, il te suffit d'envoyer un mail à l'adresse Pour contacter un⋅e trésorièr⋅e, il te suffit d'envoyer un mail à l'adresse
`tresorerie.bde@lists.crans.org <tresorerie.bde@lists.crans.org>`_. Pense bien `tresorerie.bde@lists.crans.org <tresorerie.bde@lists.crans.org>`_. Pense bien
à donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta à donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta
demande concerne un virement entre le compte du BDE et ton propre compte. demande concerne un virement entre le compte du BDE et ton propre compte.
@ -144,9 +145,9 @@ J'ai trouvé un bug, comment le signaler ?
----------------------------------------- -----------------------------------------
.. note:: .. note::
La Note Kfet est développée bénévolement par des membres du BDE. Malgré tous nos La Note Kfet est développée bénévolement par des membres du BDE. Nous mettons
efforts pour fournir une plateforme sans erreur et la plus ergonomique possible. tous nos efforts pour fournir une plateforme sans erreur et la plus ergonomique
Toutefois, il n'est évidemment pas exclu que des bugs soient présents :) possible. Toutefois, il n'est évidemment pas exclu que des bugs soient présents :)
Pour nous soumettre un bug, tu peux envoyer un mail à Pour nous soumettre un bug, tu peux envoyer un mail à
`notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_ `notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_
@ -166,14 +167,14 @@ Je souhaite contribuer
La Note Kfet est essentiellement développée par des responsables informatiques du La Note Kfet est essentiellement développée par des responsables informatiques du
BDE de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez BDE de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez
bien sûr le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est bien sûr le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est
distribuée. Pour cela, si vous êtes adhérent Crans, vous pouvez proposer une distribuée. Pour cela, si vous êtes adhérent⋅e Crans, vous pouvez proposer une
demande de fusion de votre code. Si ce n'est pas le cas, vous pouvez envoyer un demande de fusion de votre code. Si ce n'est pas le cas, vous pouvez envoyer un
mail à `notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_. mail à `notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_.
Dans les deux cas, merci de rejoindre le canal ``#note`` sur IRC :) Dans les deux cas, merci de rejoindre le canal ``#note`` sur IRC :)
Contributeurs Contributeur⋅rices
------------- ------------------
.. note:: .. note::
La version 2020 de la Note Kfet a été développée sous le mandat de la La version 2020 de la Note Kfet a été développée sous le mandat de la
@ -187,18 +188,19 @@ Contributeurs
Liste des contributeurs majeurs, par ordre alphabétique : Liste des contributeurs majeurs, par ordre alphabétique :
* Pierre-André « PAC » COMBY * Pierre-André « PAC » COMBY
* Yohann « ÿnérant » D'ANELLO * Emmy « ÿnérant » D'ANELLO
* Benjamin « esum » GRAILLOT * Benjamin « esum » GRAILLOT
* Alexandre « erdnaxe » IOOSS * Alexandre « erdnaxe » IOOSS
* Nicolas « nicomarg » MARGULIES
Hébergement Hébergement
----------- -----------
.. note:: .. note::
En accord entre de l'ENS Paris-Saclay et le Crans, l'instance de production présente En accord entre le BDE de l'ENS Paris-Saclay et le Crans, l'instance de production
sur `<https://note.crans.org/>`_ est hébergée sur l'un des serveurs du Crans. présente sur `<https://note.crans.org/>`_ est hébergée sur l'un des serveurs du
Les données sont hébergées à l'adresse : Crans. Les données sont hébergées à l'adresse :
.. code:: .. code::

View File

@ -1,12 +1,12 @@
La note, c'est quoi ? La note, c'est quoi ?
===================== =====================
La Note Kfet est un porte-monnaie virtuel proposé gratuitement à tous les adhérents BDE. La Note Kfet est un porte-monnaie virtuel proposé gratuitement à toustes les
C'est le moyen de paiement privilégié au sein du campus, que ce soit pour payer des adhérent⋅es BDE. C'est le moyen de paiement privilégié au sein du campus, que ce soit
activités du BDE, ou bien pour faire des remboursements entre amis. La note contient pour payer des activités du BDE, ou bien pour faire des remboursements entre ami⋅es.
également la base d'adhérents du BDE et contient de nombreux outils facilitant la vie La note contient également la base d'adhérent⋅es du BDE et contient de nombreux outils
des différents bureaux de clubs, en particulier les trésoriers et surtout les trésoriers facilitant la vie des différents bureaux de clubs, en particulier les trésorièr⋅es et
BDE. surtout les trésorièr⋅es BDE.
La Note Kfet est accessible à l'adresse `<https://note.crans.org>`_. La version actuelle La Note Kfet est accessible à l'adresse `<https://note.crans.org>`_. La version actuelle
a été développée par le BDE 2020-2021, et est maintenue par les respos infos du BDE de a été développée par le BDE 2020-2021, et est maintenue par les respos infos du BDE de
@ -23,7 +23,7 @@ Chaque adhérent⋅e BDE dispose d'un compte appelé « note », créé lors de
Une note est associée à un solde en euros, et à un pseudo appelé « nom de note ». Une note est associée à un solde en euros, et à un pseudo appelé « nom de note ».
Le solde associé à la note correspond au solde de l'adhérent⋅e, avec lequel il est Le solde associé à la note correspond au solde de l'adhérent⋅e, avec lequel il est
possible de payer tout ce qui est en lien avec le BDE. Le nom de note suffit à possible de payer tout ce qui est en lien avec le BDE. Le nom de note suffit à
identifier une personne, et il suffit par exemple de donner ce nom à un permanencier identifier une personne, et il suffit par exemple de donner ce nom à un⋅e permanencièr⋅e
à la Kfet pour acheter un produit. à la Kfet pour acheter un produit.
Faire une transaction Faire une transaction
@ -34,10 +34,10 @@ d'un⋅e autre adhérent⋅e, pourvu que le montant de la transaction n'excède
propre solde. Pour cela, il suffit d'aller sur la page de transferts, qui est la propre solde. Pour cela, il suffit d'aller sur la page de transferts, qui est la
page par défaut après connexion : `<https://note.crans.org/note/transfer/>`_ page par défaut après connexion : `<https://note.crans.org/note/transfer/>`_
Le formulaire pour effectuer un transfert apparaît alors. En cliquant sur le bouton Le formulaire pour effectuer un transfert apparaît alors. En cliquant sur le bouton
« Je suis l'émetteur », le champ « Émetteurs » est directement complété avec votre « Je suis l'émetteur⋅rice », le champ « Émetteur⋅rices » est directement complété
propre note. Il vous suffit alors de remplir le champ « Destinataires » avec le ou avec votre propre note. Il vous suffit alors de remplir le champ « Destinataires »
les noms de note que vous voulez créditer, de spécifier le montant et la raison de avec les noms de note que vous voulez créditer, de spécifier le montant et la raison
votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes, de votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes,
si votre solde est suffisant et que la transaction est acceptée, un message de si votre solde est suffisant et que la transaction est acceptée, un message de
confirmation apparaîtra et la transaction apparaîtra dans l'historique ci-dessous. confirmation apparaîtra et la transaction apparaîtra dans l'historique ci-dessous.
@ -49,7 +49,7 @@ Consulter ses données personnelles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
La Note Kfet sert non seulement à faire des transactions d'argent virtuel au sein La Note Kfet sert non seulement à faire des transactions d'argent virtuel au sein
du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérents. du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérent⋅es.
À cet effet, diverses informations personnelles sont collectées par la Note. À cet effet, diverses informations personnelles sont collectées par la Note.
Pour accéder à votre compte, une fois connecté⋅e, rendez-vous dans le menu en haut Pour accéder à votre compte, une fois connecté⋅e, rendez-vous dans le menu en haut
@ -79,22 +79,22 @@ présentes sont :
* Inscription aux listes de diffusion du BDE, du BDA et du BDS * Inscription aux listes de diffusion du BDE, du BDA et du BDS
Les trois premières informations sont obligatoires pour pouvoir vous contacter Les trois premières informations sont obligatoires pour pouvoir vous contacter
et pouvoir tenir un registre d'adhérent. et pouvoir tenir un registre d'adhérent⋅es.
Le numéro de téléphone et l'adresse ne sont utilisés uniquement en cas d'urgence, Le numéro de téléphone et l'adresse ne sont utilisés uniquement en cas d'urgence,
notamment en cas de WEI, et ne sont pas des champs obligatoires. De plus, notamment en cas de WEI, et ne sont pas des champs obligatoires. De plus,
promotion, département et section ne sont utilisés sérieusement que en cas promotion, département et section ne sont utilisés sérieusement que en cas
de WEI, et ne sont pas utilisés sinon. de WEI, et ne sont pas utilisés sinon.
La distinction élève/étudiant permet de distinguer les achats qui peuvent avoir La distinction élève/étudiant⋅e permet de distinguer les achats qui peuvent avoir
des prix différents pour les élèves et pour les étudiants, notamment en cas de des prix différents pour les élèves et pour les étudiant⋅es, notamment en cas de
transferts d'argent, de WEI ou d'adhésion à certains clubs. transferts d'argent, de WEI ou d'adhésion à certains clubs.
Hormis le pseudo, toutes ces informations ont un caractère confidentiel qui les Hormis le pseudo, toutes ces informations ont un caractère confidentiel qui les
rend privées et inaccessible aux utilisateurs. Les trésoriers BDE et les respos rend privées et inaccessible aux utilisateur⋅rices. Les trésorièr⋅es BDE et les respos
info ont accès à toutes les informations (en cas de besoin uniquement, le cas info ont accès à toutes les informations (en cas de besoin uniquement, le cas
contraire est considéré comme de l'abus de droit qui est punissable), et les contraire est considéré comme de l'abus de droit qui est punissable), et les
trésoriers de club ont accès au nom et au prénom afin de faciliter l'association trésorièr⋅es de club ont accès au nom et au prénom afin de faciliter l'association
à un nom de note. à un nom de note.
Dans la partie informations personnelles, il est également possible de modifier Dans la partie informations personnelles, il est également possible de modifier
@ -106,7 +106,7 @@ de nom de note.
Le tableau des adhésions actives sur la partie du haut contient l'ensemble des Le tableau des adhésions actives sur la partie du haut contient l'ensemble des
clubs auxquels vous êtes adhérent⋅es, avec les dates de début, de fin, votre clubs auxquels vous êtes adhérent⋅es, avec les dates de début, de fin, votre
rôle au sein du club (généralement simple membre, mais cela peut également rôle au sein du club (généralement simple membre, mais cela peut également
être trésorier⋅ère par exemple) ainsi que la cotisation que vous avez éventuellement être trésorière par exemple) ainsi que la cotisation que vous avez éventuellement
payée au club. payée au club.
L'historique des transactions contient l'ensemble des transactions qui ont fait L'historique des transactions contient l'ensemble des transactions qui ont fait

View File

@ -3,7 +3,7 @@ Documentation de la Note Kfet 2020
Bienvenue sur la documentation de la Note Kfet 2020. Cette documentation est à la fois Bienvenue sur la documentation de la Note Kfet 2020. Cette documentation est à la fois
destinée aux adhérents BDE pour découvrir le fonctionnement de la note, mais aussi aux destinée aux adhérent⋅es BDE pour découvrir le fonctionnement de la note, mais aussi aux
respos info qui souhaitent découvrir comment fonctionne la note sous le capot. respos info qui souhaitent découvrir comment fonctionne la note sous le capot.
Des informations complémentaires sont également disponibles sur le `Wiki Crans <https://wiki.crans.org/NoteKfet/NoteKfet2020/>`_. Des informations complémentaires sont également disponibles sur le `Wiki Crans <https://wiki.crans.org/NoteKfet/NoteKfet2020/>`_.

View File

@ -162,8 +162,8 @@ Ouvrez votre navigateur, tapez `<http://localhost:8000/>`_, enjoy :)
optimisé pour recevoir des requêtes en parallèle ou être utilisé en production. optimisé pour recevoir des requêtes en parallèle ou être utilisé en production.
Créer un super-utilisateur Créer un⋅e super-utilisateur⋅rice
-------------------------- ---------------------------------
La commande ``./manage.py createsuperuser`` vous permettra de créer un super-utilisateur La commande ``./manage.py createsuperuser`` vous permettra de créer un⋅e
initial. super-utilisateur⋅rice initial⋅e.

View File

@ -3,7 +3,7 @@ Installer la Note Kfet en production
Cette page détaille comment installer la Note Kfet sur un serveur de production, Cette page détaille comment installer la Note Kfet sur un serveur de production,
dédié uniquement à l'utilisation de la note. On supposera que le serveur tourne dédié uniquement à l'utilisation de la note. On supposera que le serveur tourne
avec un Debian Buster à jour. avec un Debian Bullseye à jour.
Ajout des dépôts buster-backports Ajout des dépôts buster-backports
@ -15,7 +15,7 @@ version 1.11, qui n'est plus maintenue par Django depuis déjà quelques mois.
Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye
utilisant la version 2.2. utilisant la version 2.2.
Afin de permettre à ses utilisateurs d'utiliser les dernières fonctionnalités de Afin de permettre à ses utilisateur⋅rices d'utiliser les dernières fonctionnalités de
certains paquets, Debian a créé une distribution particulière, appelée certains paquets, Debian a créé une distribution particulière, appelée
``buster-backports``. Cette distribution contient des paquets de la distribution ``buster-backports``. Cette distribution contient des paquets de la distribution
à venir « ``testing`` » (``bullseye`` à l'heure où cette documentation est écrite) à venir « ``testing`` » (``bullseye`` à l'heure où cette documentation est écrite)
@ -42,7 +42,7 @@ Vérifiez que les paquets sont bien récupérés, en cherchant cette ligne :
.. warning:: .. warning::
Avis aux futurs respos info : pensez à bien actualiser cette documentation lorsque Avis aux futur⋅es respos info : pensez à bien actualiser cette documentation lorsque
Debian Bullseye sera sorti. En particulier, il ne sera pas déconnant de continuer Debian Bullseye sera sorti. En particulier, il ne sera pas déconnant de continuer
à utiliser non pas buster-backports mais bullseye-backports pour installer la à utiliser non pas buster-backports mais bullseye-backports pour installer la
note avec Django 3.2 et non Django 2.2. note avec Django 3.2 et non Django 2.2.
@ -50,17 +50,24 @@ Vérifiez que les paquets sont bien récupérés, en cherchant cette ligne :
Bien sûr, vous testerez sur un serveur qui n'est pas celui utilisé avant :) Bien sûr, vous testerez sur un serveur qui n'est pas celui utilisé avant :)
.. error::
Avis aux respos info : on utilise maintenant Debian Bullseye. À voir si on veut
avoir bullseye-backports pour avoir Django 3.2 ou non. La section du haut sera à
adapter.
Installation des dépendances nécessaires Installation des dépendances nécessaires
---------------------------------------- ----------------------------------------
On s'efforce pour récupérer le plus possible de dépendances via les paquets Debian On s'efforce pour récupérer le plus possible de dépendances via les paquets Debian
plutôt que via ``pip`` afin de faciliter les mises à jour et avoir une installation plutôt que via ``pip`` afin de faciliter les mises à jour et avoir une installation
plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-backports : plus propre. On peut donc installer tout ce dont on a besoin :
.. code:: bash .. code:: bash
$ sudo apt update $ sudo apt update
$ sudo apt install -t buster-backports --no-install-recommends \ $ sudo apt install --no-install-recommends \
gettext git ipython3 \ # Dépendances basiques gettext git ipython3 \ # Dépendances basiques
fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \ python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
@ -187,7 +194,7 @@ clé sous forme de chaîne de caractère suffisamment longue (64 caractères par
qui n'est pas à transmettre et qui évite d'autres sites malveillants de faire des requêtes qui n'est pas à transmettre et qui évite d'autres sites malveillants de faire des requêtes
directement sur la note. directement sur la note.
Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅es peuvent contacter Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅es peuvent contacter
en cas de problème. C'est là où le champ ``Nous contacter`` redirigera. en cas de problème. C'est là où le champ ``Nous contacter`` redirigera.
Le champ ``NOTE_URL`` correspond au nom de domaine autorisé à accéder au site. C'est également Le champ ``NOTE_URL`` correspond au nom de domaine autorisé à accéder au site. C'est également
@ -285,7 +292,7 @@ On doit compiler les traductions (pour pouvoir les lire plus vite par la suite)
$ ./manage.py compilemessages $ ./manage.py compilemessages
Les fichiers statiques (fiches de style, fichiers Javascript, images, ...) doivent Les fichiers statiques (fiches de style, fichiers Javascript, images,) doivent
être exportées dans le dossier ``static`` : être exportées dans le dossier ``static`` :
.. code:: bash .. code:: bash
@ -636,5 +643,5 @@ On peut enfin redémarrer le serveur Web. Les données ont bien été copiées.
.. caution:: .. caution::
On ne copiera **jamais** des données d'adhérent⋅es sur une machine personnelle. On ne copiera **jamais** des données d'adhérent⋅es sur une machine personnelle.
Ce type d'opération doit s'effectuer impérativement entre des serveurs du BDE. Ce type d'opération doit s'effectuer impérativement entre des serveurs du BDE.

View File

@ -131,8 +131,8 @@ de diffusion utiles.
Il prend 2 options : Il prend 2 options :
* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``, * ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``,
``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérents ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es
actuels (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour actuel⋅les (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour
``clubs@lists.crans.org``), des personnes à abonner à ``evenements@lists.crans.org``, ``clubs@lists.crans.org``), des personnes à abonner à ``evenements@lists.crans.org``,
à ``all.bda@lists.crans.org`` et enfin à ``bds@lists.crans.org``. à ``all.bda@lists.crans.org`` et enfin à ``bds@lists.crans.org``.
* ``--lang``, qui prend en argument ``fr`` ou ``en``. N'est utile que pour la ML événements, * ``--lang``, qui prend en argument ``fr`` ou ``en``. N'est utile que pour la ML événements,
@ -146,8 +146,8 @@ malheureusement pas aussi simple que de simplement supposer que ces listes sont
À terme, il pourrait être envisageable de synchroniser automatiquement les listes avec la note. À terme, il pourrait être envisageable de synchroniser automatiquement les listes avec la note.
Suppression d'un utilisateur Suppression d'un⋅e utilisateur⋅rice
---------------------------- -----------------------------------
Le script s'appelle ``force_delete_user``. Le script s'appelle ``force_delete_user``.
@ -156,7 +156,7 @@ Le script s'appelle ``force_delete_user``.
Ce script est dangereux. À n'utiliser qu'avec de très grosses pincettes si vous savez Ce script est dangereux. À n'utiliser qu'avec de très grosses pincettes si vous savez
ce que vous faites. Seul cas d'usage pour l'instant recensé : supprimer des comptes en ce que vous faites. Seul cas d'usage pour l'instant recensé : supprimer des comptes en
double qui se sont malencontreusement retrouvés validés pour raison de Sogé et de mauvaise double qui se sont malencontreusement retrouvés validés pour raison de Sogé et de mauvaise
communication au sein des trésorier⋅ère⋅s. communication au sein des trésorièr⋅es.
Il n'est certainement pas prévu de supprimer des vrais comptes existants via ce script. Il n'est certainement pas prévu de supprimer des vrais comptes existants via ce script.
On ne supprime pas l'historique. Si jamais quelqu'un demanderait à supprimer son compte, On ne supprime pas l'historique. Si jamais quelqu'un demanderait à supprimer son compte,
@ -164,8 +164,8 @@ Le script s'appelle ``force_delete_user``.
Ce script est utile lorsqu'il faut supprimer un compte créer par erreur. Tant que la validation Ce script est utile lorsqu'il faut supprimer un compte créer par erreur. Tant que la validation
n'est pas faite, il suffit en général de cliquer sur le bouton « Supprimer le compte » sur n'est pas faite, il suffit en général de cliquer sur le bouton « Supprimer le compte » sur
l'interface de validation. Cela supprimera l'utilisateur et le profil associé, sans toucher l'interface de validation. Cela supprimera l'utilisateur⋅rice et le profil associé, sans
à une quelconque note puisqu'elle ne sera pas créée. toucher à une quelconque note puisqu'elle ne sera pas créée.
Ce script supprime donc un compte ainsi que toutes les données associées (note, alias, Ce script supprime donc un compte ainsi que toutes les données associées (note, alias,
transactions). Il n'est donc pas à prendre à la légère, et vous devez savoir ce que vous transactions). Il n'est donc pas à prendre à la légère, et vous devez savoir ce que vous
@ -181,8 +181,8 @@ pour supprimer tout ce qu'il faut, et une validation manuelle sera requise pour
la suppression. L'option ``--doit`` évite cette confirmation manuelle. la suppression. L'option ``--doit`` évite cette confirmation manuelle.
**Vous n'avez jamais à utiliser cette option en théorie.** **Vous n'avez jamais à utiliser cette option en théorie.**
À la fin du processus, un mail est envoyé aux administrateurs pour les prévenir des À la fin du processus, un mail est envoyé aux administrateur⋅rices pour les prévenir des
élements supprimés. éléments supprimés.
Des données réelles jamais tu ne supprimeras. Des données réelles jamais tu ne supprimeras.
@ -199,14 +199,14 @@ la Note Kfet 2015.
s'est déroulé. s'est déroulé.
Ajouter un super-utilisateur Ajouter un⋅e super-utilisateur⋅rice
---------------------------- -----------------------------------
Le script s'appelle ``make_su``. Le script s'appelle ``make_su``.
Il prend en argument un pseudo. Il prend en argument un pseudo.
Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur, Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur⋅rice,
et obtiens donc les pleins pouvoirs sur la note. À ne donner qu'aux respos info. et obtiens donc les pleins pouvoirs sur la note. À ne donner qu'aux respos info.
Avec l'option ``--STAFF, -s``, la personne avec ce pseudo acquiert le statut équipe, Avec l'option ``--STAFF, -s``, la personne avec ce pseudo acquiert le statut équipe,
@ -253,20 +253,20 @@ Envoi des rappels de négatif
Le script s'appelle ``send_mail_to_negative_balances``. Le script s'appelle ``send_mail_to_negative_balances``.
Il sert à rappeler aux adhérent⋅es et clubs en négatif qu'ils le sont, mais également Il sert à rappeler aux adhérent⋅es et clubs en négatif qui le sont, mais également
à envoyer aux trésorier⋅ère⋅s et respos info la liste des adhérent⋅es en négatif. à envoyer aux trésorièr⋅es et respos info la liste des adhérent⋅es en négatif.
Il prend les options suivantes : Il prend les options suivantes :
* ``--spam, -s`` : envoie à chaque adhérent⋅e en négatif un rappel par mail pour recharger * ``--spam, -s`` : envoie à chaque adhérent⋅e en négatif un rappel par mail pour recharger
* ``--report, -r`` : envoie le rapport aux trésorier⋅ère⋅s et respos info * ``--report, -r`` : envoie le rapport aux trésorièr⋅es et respos info
* ``--negative-amount,-n`` : définit le solde maximal en-dessous duquel les notes * ``--negative-amount,-n`` : définit le solde maximal en-dessous duquel les notes
apparaitront sur le rapport / seront spammées apparaitront sur le rapport / seront spammées
* ``--add-years, -y`` : ajoute également les adhérent⋅es des ``n`` dernières années * ``--add-years, -y`` : ajoute également les adhérent⋅es des ``n`` dernières années
Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅rices en Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅ices en
négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅es ou de négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅es ou de
vieux/vieilles adhérent⋅es de moins d'un an sous -10 €. vieilleux adhérent⋅es de moins d'un an sous -10 €.
Envoi des rapports Envoi des rapports
@ -274,8 +274,8 @@ Envoi des rapports
Le script s'appelle ``send_reports``. Le script s'appelle ``send_reports``.
Les utilisateurs ont la possibilité de recevoir sur demande un rapport à la fréquence de Les utilisateur⋅rices ont la possibilité de recevoir sur demande un rapport à la
leur choix (en jours) des transactions effectuées sur leur note. fréquence de leur choix (en jours) des transactions effectuées sur leur note.
Le script prend 2 options : Le script prend 2 options :

View File

@ -19,7 +19,7 @@ python3 manage.py migrate
if [ "$1" ]; then if [ "$1" ]; then
# Command passed # Command passed
echo "Running $@..." echo "Running $@"
$@ $@
else else
# Launch server # Launch server

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-15 23:21+0100\n" "POT-Creation-Date: 2020-11-15 23:21+0100\n"
"PO-Revision-Date: 2020-11-16 20:21+0000\n" "PO-Revision-Date: 2020-11-16 20:21+0000\n"
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n" "Last-Translator: Emmy D'ANELLO <ynerant@crans.org>\n"
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>" "Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>"
"\n" "\n"
"Language: de\n" "Language: de\n"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ MAILTO=notekfet2020@lists.crans.org
30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki -v 0 30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki -v 0
# Spammer les gens en négatif # Spammer les gens en négatif
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0
# Envoyer le rapport mensuel aux trésoriers et respos info # Envoyer le rapport mensuel aux trésorièr⋅es et respos info
00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0 00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0
# Envoyer les rapports aux gens # Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0

View File

@ -6,7 +6,7 @@
import os import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR,)
from datetime import timedelta from datetime import timedelta
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@ -24,6 +24,15 @@ ALLOWED_HOSTS = [
os.getenv('NOTE_URL', 'localhost'), os.getenv('NOTE_URL', 'localhost'),
] ]
# Use secure cookies in production
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
# Remember HTTPS for 1 year
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Application definition # Application definition

View File

@ -12,7 +12,7 @@
import os import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR,)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if os.getenv("DJANGO_DEV_STORE_METHOD", "sqlite") != "postgresql": if os.getenv("DJANGO_DEV_STORE_METHOD", "sqlite") != "postgresql":

View File

@ -65,7 +65,7 @@ mark {
/* Last BDE colors */ /* Last BDE colors */
.bg-primary { .bg-primary {
background-color: rgb(18, 67, 4) !important; background-color: rgb(102, 83, 105) !important;
} }
html { html {
@ -81,14 +81,14 @@ body {
.btn-outline-primary:not(:disabled):not(.disabled).active, .btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active { .btn-outline-primary:not(:disabled):not(.disabled):active {
color: #fff; color: #fff;
background-color: rgb(18, 67, 46); background-color: rgb(102, 83, 105);
border-color: rgb(18, 67, 46); border-color: rgb(102, 83, 105);
} }
.btn-outline-primary { .btn-outline-primary {
color: rgb(18, 67, 46); color: rgb(102, 83, 105);
background-color: rgba(248, 249, 250, 0.9); background-color: rgba(248, 249, 250, 0.9);
border-color: rgb(18, 67, 46); border-color: rgb(102, 83, 105);
} }
.turbolinks-progress-bar { .turbolinks-progress-bar {
@ -99,35 +99,35 @@ body {
.btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active { .btn-primary:not(:disabled):not(.disabled):active {
color: #fff; color: #fff;
background-color: rgb(18, 67, 46); background-color: rgb(102, 83, 105);
border-color: rgb(18, 67, 46); border-color: rgb(102, 83, 105);
} }
.btn-primary { .btn-primary {
color: rgba(248, 249, 250, 0.9); color: rgba(248, 249, 250, 0.9);
background-color: rgb(28, 114, 10); background-color: rgb(102, 83, 105);
border-color: rgb(18, 67, 46); border-color: rgb(102, 83, 105);
} }
.border-primary { .border-primary {
border-color: rgb(28, 114, 10) !important; border-color: rgb(115, 15, 115) !important;
} }
a { a {
color: rgb(28, 114, 10); color: rgb(102, 83, 105);
} }
a:hover { a:hover {
color: rgb(122, 163, 75); color: rgb(200, 30, 200);
} }
.form-control:focus { .form-control:focus {
box-shadow: 0 0 0 0.25rem rgba(122, 163, 75, 0.25); box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.25);
border-color: rgb(122, 163, 75); border-color: rgb(200, 30, 200);
} }
.btn-outline-primary.focus { .btn-outline-primary.focus {
box-shadow: 0 0 0 0.25rem rgba(122, 163, 75, 0.5); box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.5);
} }

View File

@ -13,21 +13,29 @@ $(document).ready(function () {
$('#' + prefix + '_reset').removeClass('d-none') $('#' + prefix + '_reset').removeClass('d-none')
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) { $.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
let html = '' let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">'
objects.results.forEach(function (obj) { objects.results.forEach(function (obj) {
html += li(prefix + '_' + obj.id, obj[name_field]) html += li(prefix + '_' + obj.id, obj[name_field])
}) })
html += '</ul>'
const results_list = $('#' + prefix + '_list') target.tooltip({
results_list.html(html) html: true,
placement: 'bottom',
trigger: 'manual',
container: target.parent(),
fallbackPlacement: 'clockwise'
})
target.attr("data-original-title", html).tooltip("show")
objects.results.forEach(function (obj) { objects.results.forEach(function (obj) {
$('#' + prefix + '_' + obj.id).click(function () { $('#' + prefix + '_' + obj.id).click(function () {
target.val(obj[name_field]) target.val(obj[name_field])
$('#' + prefix + '_pk').val(obj.id) $('#' + prefix + '_pk').val(obj.id)
results_list.html('') target.tooltip("hide")
target.removeClass('is-invalid') target.removeClass('is-invalid')
target.addClass('is-valid') target.addClass('is-valid')
@ -37,8 +45,8 @@ $(document).ready(function () {
if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) } if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) }
}) })
if (results_list.children().length === 1 && e.originalEvent.keyCode >= 32) { if (objects.results.length === 1 && e.originalEvent.keyCode >= 32) {
results_list.children().first().trigger('click') $('#' + prefix + '_' + objects.results[0].id).trigger('click')
} }
}) })
}) })

View File

@ -196,7 +196,7 @@ function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_p
field.tooltip({ field.tooltip({
html: true, html: true,
placement: 'bottom', placement: 'bottom',
title: 'Loading...', title: 'Loading',
trigger: 'manual', trigger: 'manual',
container: field.parent(), container: field.parent(),
fallbackPlacement: 'clockwise' fallbackPlacement: 'clockwise'

View File

@ -9,9 +9,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
name="{{ widget.name }}_name" autocomplete="off" name="{{ widget.name }}_name" autocomplete="off"
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}> {% endfor %}
aria-describedby="{{widget.attrs.id}}_tooltip">
{% if widget.resetable %} {% if widget.resetable %}
<a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset{% if not widget.value %} d-none{% endif %}">{% trans "Reset" %}</a> <a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset{% if not widget.value %} d-none{% endif %}">{% trans "Reset" %}</a>
{% endif %} {% endif %}
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
</ul>

View File

@ -179,7 +179,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{# TODO Add banners #} {# TODO Add banners #}
</div> </div>
{% block content %} {% block content %}
<p>Default content...</p> <p>Default content</p>
{% endblock %} {% endblock %}
</div> </div>
</main> </main>