mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 09:58:23 +02:00
Compare commits
79 Commits
svg_icons
...
fix_pipeli
Author | SHA1 | Date | |
---|---|---|---|
81c12436a8 | |||
8eee14075c | |||
ab9ba62af1 | |||
8ca0648e75 | |||
77ecfd6ed5 | |||
0ab1367e55 | |||
524f0e098a | |||
1ca4246cbd | |||
6a9021ec14 | |||
cb74311e7b | |||
9d7dd566c9 | |||
9944ebcaad | |||
8537f043f7 | |||
c89a95f8d2 | |||
73640b1dfa | |||
84b16ab603 | |||
6a1b51dbbf | |||
c441a43a8b | |||
87f3b51b04 | |||
0a853fd3e6 | |||
c429734810 | |||
5d759111b6 | |||
70baf7566c | |||
eb355f547c | |||
7068170f18 | |||
45ee9a8941 | |||
454ea19603 | |||
5a77a66391 | |||
761fc170eb
|
|||
ac23d7eb54
|
|||
40e7415062
|
|||
319405d2b1
|
|||
633ab88b04
|
|||
e29b42eecc
|
|||
dc69faaf1d
|
|||
442a5c5e36
|
|||
7ab0fec3bc
|
|||
bd4fb23351 | |||
ee22e9b3b6 | |||
19ae616fb4 | |||
b7657ec362 | |||
4d03d9460d | |||
3633f66a87 | |||
d43fbe7ac6 | |||
df5f9b5f1e | |||
4161248bff
|
|||
58136f3c48
|
|||
d9b4e0a9a9
|
|||
8563a8d235
|
|||
5f69232560 | |||
d3273e9ee2
|
|||
4e30f805a7 | |||
546e422e64
|
|||
9048a416df
|
|||
8578bd743c
|
|||
45a10dad00
|
|||
18a1282773
|
|||
132afc3d15
|
|||
6bf16a181a
|
|||
e20df82346
|
|||
1eb72044c2 | |||
f88eae924c
|
|||
4b6e3ba546
|
|||
bf0fe3479f | |||
45ba4f9537
|
|||
b204805ce2
|
|||
2f28e34cec
|
|||
9c8ea2cd41
|
|||
41289857b2 | |||
28a8792c9f
|
|||
58cafad032
|
|||
7848cd9cc2
|
|||
d18ccfac23
|
|||
e479e1e3a4 | |||
82b0c83b1f | |||
38ca414ef6
|
|||
fd811053c7
|
|||
9d386d1ecf
|
|||
ca2b9f061c |
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,6 +42,7 @@ map.json
|
|||||||
backups/
|
backups/
|
||||||
/static/
|
/static/
|
||||||
/media/
|
/media/
|
||||||
|
/tmp/
|
||||||
|
|
||||||
# Virtualenv
|
# Virtualenv
|
||||||
env/
|
env/
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# NoteKfet 2020
|
# NoteKfet 2020
|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||||
|
|
||||||
## Table des matières
|
## Table des matières
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
prompt: "Password of the database (leave it blank to skip database init)"
|
prompt: "Password of the database (leave it blank to skip database init)"
|
||||||
private: yes
|
private: yes
|
||||||
vars:
|
vars:
|
||||||
mirror: mirror.crans.org
|
mirror: eclats.crans.org
|
||||||
roles:
|
roles:
|
||||||
- 1-apt-basic
|
- 1-apt-basic
|
||||||
- 2-nk20
|
- 2-nk20
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
note:
|
note:
|
||||||
server_name: note.crans.org
|
server_name: note.crans.org
|
||||||
git_branch: master
|
git_branch: main
|
||||||
serve_static: true
|
serve_static: true
|
||||||
cron_enabled: true
|
cron_enabled: true
|
||||||
email: notekfet2020@lists.crans.org
|
email: notekfet2020@lists.crans.org
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
---
|
---
|
||||||
- name: Add buster-backports to apt sources
|
- name: Add buster-backports to apt sources if needed
|
||||||
apt_repository:
|
apt_repository:
|
||||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||||
state: present
|
state: present
|
||||||
when: ansible_facts['distribution'] == "Debian"
|
when:
|
||||||
|
- ansible_distribution == "Debian"
|
||||||
|
- ansible_distribution_major_version | int == 10
|
||||||
|
|
||||||
- name: Install note_kfet APT dependencies
|
- name: Install note_kfet APT dependencies
|
||||||
apt:
|
apt:
|
||||||
update_cache: true
|
update_cache: true
|
||||||
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
|
||||||
install_recommends: false
|
install_recommends: false
|
||||||
name:
|
name:
|
||||||
# Common tools
|
# Common tools
|
||||||
|
@ -168,6 +168,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
Don't display the entry interface if the user has no right to see it (no right to add an entry for itself),
|
Don't display the entry interface if the user has no right to see it (no right to add an entry for itself),
|
||||||
it is closed or doesn't manage entries.
|
it is closed or doesn't manage entries.
|
||||||
"""
|
"""
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return self.handle_no_permission()
|
||||||
|
|
||||||
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
||||||
|
|
||||||
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
||||||
|
5
apps/api/pagination.py
Normal file
5
apps/api/pagination.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
|
class CustomPagination(PageNumberPagination):
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
|
@ -7,8 +7,11 @@ from django.contrib.auth.models import User
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from member.api.serializers import ProfileSerializer, MembershipSerializer
|
from member.api.serializers import ProfileSerializer, MembershipSerializer
|
||||||
|
from member.models import Membership
|
||||||
from note.api.serializers import NoteSerializer
|
from note.api.serializers import NoteSerializer
|
||||||
from note.models import Alias
|
from note.models import Alias
|
||||||
|
from note_kfet.middlewares import get_current_request
|
||||||
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
@ -45,18 +48,30 @@ class OAuthSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
normalized_name = serializers.SerializerMethodField()
|
normalized_name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
profile = ProfileSerializer()
|
profile = serializers.SerializerMethodField()
|
||||||
|
|
||||||
note = NoteSerializer()
|
note = serializers.SerializerMethodField()
|
||||||
|
|
||||||
memberships = serializers.SerializerMethodField()
|
memberships = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_normalized_name(self, obj):
|
def get_normalized_name(self, obj):
|
||||||
return Alias.normalize(obj.username)
|
return Alias.normalize(obj.username)
|
||||||
|
|
||||||
|
def get_profile(self, obj):
|
||||||
|
# Display the profile of the user only if we have rights to see it.
|
||||||
|
return ProfileSerializer().to_representation(obj.profile) \
|
||||||
|
if PermissionBackend.check_perm(get_current_request(), 'member.view_profile', obj.profile) else None
|
||||||
|
|
||||||
|
def get_note(self, obj):
|
||||||
|
# Display the note of the user only if we have rights to see it.
|
||||||
|
return NoteSerializer().to_representation(obj.note) \
|
||||||
|
if PermissionBackend.check_perm(get_current_request(), 'note.view_note', obj.note) else None
|
||||||
|
|
||||||
def get_memberships(self, obj):
|
def get_memberships(self, obj):
|
||||||
|
# Display only memberships that we are allowed to see.
|
||||||
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
|
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
|
||||||
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now()))
|
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now())
|
||||||
|
.filter(PermissionBackend.filter_queryset(get_current_request(), Membership, 'view')))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
18
apps/member/migrations/0008_auto_20211005_1544.py
Normal file
18
apps/member/migrations/0008_auto_20211005_1544.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.24 on 2021-10-05 13:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('member', '0007_auto_20210313_1235'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profile',
|
||||||
|
name='department',
|
||||||
|
field=models.CharField(choices=[('A0', 'Informatics (A0)'), ('A1', 'Mathematics (A1)'), ('A2', 'Physics (A2)'), ("A'2", "Applied physics (A'2)"), ("A''2", "Chemistry (A''2)"), ('A3', 'Biology (A3)'), ('B1234', 'SAPHIRE (B1234)'), ('B1', 'Mechanics (B1)'), ('B2', 'Civil engineering (B2)'), ('B3', 'Mechanical engineering (B3)'), ('B4', 'EEA (B4)'), ('C', 'Design (C)'), ('D2', 'Economy-management (D2)'), ('D3', 'Social sciences (D3)'), ('E', 'English (E)'), ('EXT', 'External (EXT)')], max_length=8, verbose_name='department'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/member/migrations/0009_auto_20220904_2325.py
Normal file
18
apps/member/migrations/0009_auto_20220904_2325.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.26 on 2022-09-04 21:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('member', '0008_auto_20211005_1544'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profile',
|
||||||
|
name='promotion',
|
||||||
|
field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
||||||
|
),
|
||||||
|
]
|
@ -258,16 +258,18 @@ class Club(models.Model):
|
|||||||
This function is called each time the club detail view is displayed.
|
This function is called each time the club detail view is displayed.
|
||||||
Update the year of the membership dates.
|
Update the year of the membership dates.
|
||||||
"""
|
"""
|
||||||
if not self.membership_start:
|
if not self.membership_start or not self.membership_end:
|
||||||
return
|
return
|
||||||
|
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
|
|
||||||
if (today - self.membership_start).days >= 365:
|
if (today - self.membership_start).days >= 365:
|
||||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
if self.membership_start:
|
||||||
self.membership_start.month, self.membership_start.day)
|
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||||
self.membership_end = datetime.date(self.membership_end.year + 1,
|
self.membership_start.month, self.membership_start.day)
|
||||||
self.membership_end.month, self.membership_end.day)
|
if self.membership_end:
|
||||||
|
self.membership_end = datetime.date(self.membership_end.year + 1,
|
||||||
|
self.membership_end.month, self.membership_end.day)
|
||||||
self._force_save = True
|
self._force_save = True
|
||||||
self.save(force_update=True)
|
self.save(force_update=True)
|
||||||
|
|
||||||
|
53
apps/member/static/member/js/trust.js
Normal file
53
apps/member/static/member/js/trust.js
Normal 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)
|
||||||
|
})
|
@ -120,7 +120,7 @@ class MembershipTable(tables.Table):
|
|||||||
club=record.club,
|
club=record.club,
|
||||||
user=record.user,
|
user=record.user,
|
||||||
date_start__gte=record.club.membership_start,
|
date_start__gte=record.club.membership_start,
|
||||||
date_end__lte=record.club.membership_end,
|
date_end__lte=record.club.membership_end or date(9999, 12, 31),
|
||||||
).exists(): # If the renew is not yet performed
|
).exists(): # If the renew is not yet performed
|
||||||
empty_membership = Membership(
|
empty_membership = Membership(
|
||||||
club=record.club,
|
club=record.club,
|
||||||
|
@ -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>
|
||||||
|
41
apps/member/templates/member/profile_trust.html
Normal file
41
apps/member/templates/member/profile_trust.html
Normal 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%}
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
@ -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
|
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
|
||||||
@ -174,7 +175,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
||||||
# Don't log these tests
|
# Don't log these tests
|
||||||
modified_note._no_signal = True
|
modified_note._no_signal = True
|
||||||
modified_note.is_active = True
|
modified_note.is_active = False
|
||||||
modified_note.inactivity_reason = 'manual'
|
modified_note.inactivity_reason = 'manual'
|
||||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||||
@ -183,14 +184,14 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
modified_note._force_save = True
|
modified_note._force_save = True
|
||||||
modified_note.save()
|
modified_note.save()
|
||||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request, "note.change_note_is_active", modified_note)
|
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||||
old_note._force_save = True
|
old_note._force_save = True
|
||||||
old_note._no_signal = True
|
old_note._no_signal = True
|
||||||
old_note.save()
|
old_note.save()
|
||||||
modified_note.refresh_from_db()
|
modified_note.refresh_from_db()
|
||||||
modified_note.is_active = True
|
modified_note.is_active = True
|
||||||
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
|
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request, "note.change_note_is_active", modified_note)
|
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -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.
|
||||||
@ -256,7 +290,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(
|
context["aliases"] = AliasTable(
|
||||||
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
|
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
|
||||||
|
.order_by('normalized_name').all())
|
||||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||||
note=context["object"].note,
|
note=context["object"].note,
|
||||||
name="",
|
name="",
|
||||||
@ -403,9 +438,12 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
"""
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
club = context["club"]
|
club = self.object
|
||||||
|
context["note"] = club.note
|
||||||
|
|
||||||
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||||
club.update_membership_dates()
|
club.update_membership_dates()
|
||||||
|
|
||||||
# managers list
|
# managers list
|
||||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||||
date_start__lte=date.today(), date_end__gte=date.today())\
|
date_start__lte=date.today(), date_end__gte=date.today())\
|
||||||
@ -443,6 +481,29 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context["can_add_members"] = PermissionBackend()\
|
context["can_add_members"] = PermissionBackend()\
|
||||||
.has_perm(self.request.user, "member.add_membership", empty_membership)
|
.has_perm(self.request.user, "member.add_membership", empty_membership)
|
||||||
|
|
||||||
|
# Check permissions to see if the authenticated user can lock/unlock the note
|
||||||
|
with transaction.atomic():
|
||||||
|
modified_note = NoteClub.objects.get(pk=club.note.pk)
|
||||||
|
# Don't log these tests
|
||||||
|
modified_note._no_signal = True
|
||||||
|
modified_note.is_active = False
|
||||||
|
modified_note.inactivity_reason = 'manual'
|
||||||
|
context["can_lock_note"] = club.note.is_active and PermissionBackend \
|
||||||
|
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||||
|
old_note = NoteClub.objects.select_for_update().get(pk=club.note.pk)
|
||||||
|
modified_note.inactivity_reason = 'forced'
|
||||||
|
modified_note._force_save = True
|
||||||
|
modified_note.save()
|
||||||
|
context["can_force_lock"] = club.note.is_active and PermissionBackend \
|
||||||
|
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||||
|
old_note._force_save = True
|
||||||
|
old_note._no_signal = True
|
||||||
|
old_note.save()
|
||||||
|
modified_note.refresh_from_db()
|
||||||
|
modified_note.is_active = True
|
||||||
|
context["can_unlock_note"] = not club.note.is_active and PermissionBackend \
|
||||||
|
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
27
apps/note/migrations/0006_trust.py
Normal file
27
apps/note/migrations/0006_trust.py
Normal 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': 'frienship',
|
||||||
|
'verbose_name_plural': 'friendships',
|
||||||
|
'unique_together': {('trusting', 'trusted')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -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',
|
||||||
|
@ -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 = _("frienship")
|
||||||
|
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.
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
import html
|
import html
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html, mark_safe
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
from django.utils.translation import gettext_lazy as _
|
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 = {
|
||||||
@ -197,6 +222,17 @@ class ButtonTable(tables.Table):
|
|||||||
verbose_name=_("Edit"),
|
verbose_name=_("Edit"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hideshow = tables.Column(
|
||||||
|
verbose_name=_("Hide/Show"),
|
||||||
|
accessor="pk",
|
||||||
|
attrs={
|
||||||
|
'td': {
|
||||||
|
'class': 'col-sm-1',
|
||||||
|
'id': lambda record: "hideshow_" + str(record.pk),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||||
extra_context={"delete_trans": _('delete')},
|
extra_context={"delete_trans": _('delete')},
|
||||||
attrs={'td': {'class': 'col-sm-1'}},
|
attrs={'td': {'class': 'col-sm-1'}},
|
||||||
@ -204,3 +240,16 @@ class ButtonTable(tables.Table):
|
|||||||
|
|
||||||
def render_amount(self, value):
|
def render_amount(self, value):
|
||||||
return pretty_money(value)
|
return pretty_money(value)
|
||||||
|
|
||||||
|
def order_category(self, queryset, is_descending):
|
||||||
|
return queryset.order_by(f"{'-' if is_descending else ''}category__name"), True
|
||||||
|
|
||||||
|
def render_hideshow(self, record):
|
||||||
|
val = '<button id="'
|
||||||
|
val += str(record.pk)
|
||||||
|
val += '" class="btn btn-secondary btn-sm" \
|
||||||
|
onclick="hideshow(' + str(record.id) + ',' + \
|
||||||
|
str(record.display).lower() + ')">'
|
||||||
|
val += str(_("Hide/Show"))
|
||||||
|
val += '</button>'
|
||||||
|
return mark_safe(val)
|
||||||
|
@ -31,29 +31,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
function refreshMatchedWords() {
|
||||||
|
$("tr").each(function() {
|
||||||
|
let pattern = $('#search_field').val();
|
||||||
|
if (pattern) {
|
||||||
|
$(this).find("td:eq(0), td:eq(1), td:eq(3), td:eq(6)").each(function () {
|
||||||
|
$(this).html($(this).text().replace(new RegExp(pattern, 'i'), "<mark>$&</mark>"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
let pattern = $('#search_field').val();
|
||||||
|
$("#buttons_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + " #buttons_table", refreshMatchedWords);
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
let searchbar_obj = $("#search_field");
|
let searchbar_obj = $("#search_field");
|
||||||
let timer_on = false;
|
let timer_on = false;
|
||||||
let timer;
|
let timer;
|
||||||
|
|
||||||
function refreshMatchedWords() {
|
|
||||||
$("tr").each(function() {
|
|
||||||
let pattern = searchbar_obj.val();
|
|
||||||
if (pattern) {
|
|
||||||
$(this).find("td:eq(0), td:eq(1), td:eq(3), td:eq(6)").each(function () {
|
|
||||||
$(this).html($(this).text().replace(new RegExp(pattern, 'i'), "<mark>$&</mark>"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshMatchedWords();
|
refreshMatchedWords();
|
||||||
|
|
||||||
function reloadTable() {
|
|
||||||
let pattern = searchbar_obj.val();
|
|
||||||
$("#buttons_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + " #buttons_table", refreshMatchedWords);
|
|
||||||
}
|
|
||||||
|
|
||||||
searchbar_obj.keyup(function() {
|
searchbar_obj.keyup(function() {
|
||||||
if (timer_on)
|
if (timer_on)
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
@ -77,5 +77,28 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
addMsg('{% trans "Unable to delete button "%} #' + button_id, 'danger')
|
addMsg('{% trans "Unable to delete button "%} #' + button_id, 'danger')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on click of button "hide/show", call the API
|
||||||
|
function hideshow(id, displayed) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/note/transaction/template/' + id + '/',
|
||||||
|
type: 'PATCH',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFTOKEN': CSRF_TOKEN
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
display: !displayed
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
if(displayed)
|
||||||
|
addMsg("{% trans "Button hidden"%}", 'success', 1000)
|
||||||
|
else addMsg("{% trans "Button displayed"%}", 'success', 1000)
|
||||||
|
reloadTable()
|
||||||
|
},
|
||||||
|
error: function (err) {
|
||||||
|
addMsg("{% trans "An error occured"%}", 'danger')
|
||||||
|
}})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -90,9 +90,9 @@ class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, Sing
|
|||||||
if "search" in self.request.GET:
|
if "search" in self.request.GET:
|
||||||
pattern = self.request.GET["search"]
|
pattern = self.request.GET["search"]
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
Q(name__iregex="^" + pattern)
|
Q(name__iregex=pattern)
|
||||||
| Q(destination__club__name__iregex="^" + pattern)
|
| Q(destination__club__name__iregex=pattern)
|
||||||
| Q(category__name__iregex="^" + pattern)
|
| Q(category__name__iregex=pattern)
|
||||||
| Q(description__iregex=pattern)
|
| Q(description__iregex=pattern)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -977,7 +977,7 @@
|
|||||||
],
|
],
|
||||||
"query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]",
|
"query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]",
|
||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 2,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": false,
|
||||||
"description": "Voir les transactions d'un club"
|
"description": "Voir les transactions d'un club"
|
||||||
@ -1967,7 +1967,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"transaction"
|
"transaction"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]]",
|
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]]",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 2,
|
"mask": 2,
|
||||||
"field": "valid",
|
"field": "valid",
|
||||||
@ -2511,7 +2511,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"noteuser"
|
"noteuser"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"inactivity_reason\": null}]]",
|
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "is_active",
|
"field": "is_active",
|
||||||
@ -2527,7 +2527,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"noteuser"
|
"noteuser"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"inactivity_reason\": null}]]",
|
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "inactivity_reason",
|
"field": "inactivity_reason",
|
||||||
@ -2607,7 +2607,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"transaction"
|
"transaction"
|
||||||
],
|
],
|
||||||
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]",
|
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 2,
|
"mask": 2,
|
||||||
"field": "valid",
|
"field": "valid",
|
||||||
@ -2623,7 +2623,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"transaction"
|
"transaction"
|
||||||
],
|
],
|
||||||
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]",
|
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 2,
|
"mask": 2,
|
||||||
"field": "invalidity_reason",
|
"field": "invalidity_reason",
|
||||||
@ -2871,6 +2871,214 @@
|
|||||||
"description": "Changer l'image de n'importe quelle note"
|
"description": "Changer l'image de n'importe quelle note"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 184,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"note",
|
||||||
|
"noteclub"
|
||||||
|
],
|
||||||
|
"query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 3,
|
||||||
|
"field": "is_active",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "(Dé)bloquer la note de son club manuellement"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 185,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"note",
|
||||||
|
"noteclub"
|
||||||
|
],
|
||||||
|
"query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 3,
|
||||||
|
"field": "inactivity_reason",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "(Dé)bloquer la note de son club et indiquer que cela a été fait manuellement"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 186,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"oauth2_provider",
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"query": "{\"user\": [\"user\"]}",
|
||||||
|
"type": "view",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "Voir ses applications OAuth2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 187,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"oauth2_provider",
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"query": "{\"user\": [\"user\"]}",
|
||||||
|
"type": "create",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "Créer une application OAuth2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 188,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"oauth2_provider",
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"query": "{\"user\": [\"user\"]}",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "Modifier une application OAuth2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 189,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"oauth2_provider",
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"query": "{\"user\": [\"user\"]}",
|
||||||
|
"type": "delete",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "",
|
||||||
|
"permanent": true,
|
||||||
|
"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érents"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
@ -2901,7 +3109,15 @@
|
|||||||
126,
|
126,
|
||||||
161,
|
161,
|
||||||
162,
|
162,
|
||||||
165
|
165,
|
||||||
|
186,
|
||||||
|
187,
|
||||||
|
188,
|
||||||
|
189,
|
||||||
|
190,
|
||||||
|
191,
|
||||||
|
195,
|
||||||
|
196
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2942,7 +3158,9 @@
|
|||||||
158,
|
158,
|
||||||
159,
|
159,
|
||||||
160,
|
160,
|
||||||
179
|
179,
|
||||||
|
189,
|
||||||
|
190
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3010,7 +3228,9 @@
|
|||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
168,
|
168,
|
||||||
182
|
182,
|
||||||
|
184,
|
||||||
|
185
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3090,7 +3310,10 @@
|
|||||||
176,
|
176,
|
||||||
177,
|
177,
|
||||||
178,
|
178,
|
||||||
183
|
188,
|
||||||
|
183,
|
||||||
|
186,
|
||||||
|
187
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3278,7 +3501,20 @@
|
|||||||
180,
|
180,
|
||||||
181,
|
181,
|
||||||
182,
|
182,
|
||||||
183
|
183,
|
||||||
|
184,
|
||||||
|
185,
|
||||||
|
186,
|
||||||
|
187,
|
||||||
|
188,
|
||||||
|
189,
|
||||||
|
190,
|
||||||
|
191,
|
||||||
|
192,
|
||||||
|
193,
|
||||||
|
194,
|
||||||
|
195,
|
||||||
|
196
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3338,7 +3574,8 @@
|
|||||||
45,
|
45,
|
||||||
46,
|
46,
|
||||||
148,
|
148,
|
||||||
149
|
149,
|
||||||
|
182
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 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 oauth2_provider.oauth2_validators import OAuth2Validator
|
||||||
from oauth2_provider.scopes import BaseScopes
|
from oauth2_provider.scopes import BaseScopes
|
||||||
from member.models import Club
|
from member.models import Club
|
||||||
from note_kfet.middlewares import get_current_request
|
from note_kfet.middlewares import get_current_request
|
||||||
@ -32,3 +32,26 @@ class PermissionScopes(BaseScopes):
|
|||||||
return []
|
return []
|
||||||
return [f"{p.id}_{p.membership.club.id}"
|
return [f"{p.id}_{p.membership.club.id}"
|
||||||
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
|
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionOAuth2Validator(OAuth2Validator):
|
||||||
|
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
User can request as many scope as he wants, including invalid scopes,
|
||||||
|
but it will have only the permissions he has.
|
||||||
|
|
||||||
|
This allows clients to request more permission to get finally a
|
||||||
|
subset of permissions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
valid_scopes = set()
|
||||||
|
|
||||||
|
for t in Permission.PERMISSION_TYPES:
|
||||||
|
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0]):
|
||||||
|
scope = f"{p.id}_{p.membership.club.id}"
|
||||||
|
if scope in scopes:
|
||||||
|
valid_scopes.add(scope)
|
||||||
|
|
||||||
|
request.scopes = valid_scopes
|
||||||
|
|
||||||
|
return valid_scopes
|
||||||
|
@ -11,25 +11,25 @@
|
|||||||
<div class="accordion" id="accordionApps">
|
<div class="accordion" id="accordionApps">
|
||||||
{% for app, app_scopes in scopes.items %}
|
{% for app, app_scopes in scopes.items %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header" id="app-{{ app.name.lower }}-title">
|
<div class="card-header" id="app-{{ app.name|slugify }}-title">
|
||||||
<a class="text-decoration-none collapsed" href="#" data-toggle="collapse"
|
<a class="text-decoration-none collapsed" href="#" data-toggle="collapse"
|
||||||
data-target="#app-{{ app.name.lower }}" aria-expanded="false"
|
data-target="#app-{{ app.name|slugify }}" aria-expanded="false"
|
||||||
aria-controls="app-{{ app.name.lower }}">
|
aria-controls="app-{{ app.name|slugify }}">
|
||||||
{{ app.name }}
|
{{ app.name }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse" id="app-{{ app.name.lower }}" aria-labelledby="app-{{ app.name.lower }}" data-target="#accordionApps">
|
<div class="collapse" id="app-{{ app.name|slugify }}" aria-labelledby="app-{{ app.name|slugify }}" data-target="#accordionApps">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% for scope_id, scope_desc in app_scopes.items %}
|
{% for scope_id, scope_desc in app_scopes.items %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-check-label" for="scope-{{ app.name.lower }}-{{ scope_id }}">
|
<label class="form-check-label" for="scope-{{ app.name|slugify }}-{{ scope_id }}">
|
||||||
<input type="checkbox" id="scope-{{ app.name.lower }}-{{ scope_id }}"
|
<input type="checkbox" id="scope-{{ app.name|slugify }}-{{ scope_id }}"
|
||||||
name="scope-{{ app.name.lower }}" class="checkboxinput form-check-input" value="{{ scope_id }}">
|
name="scope-{{ app.name|slugify }}" class="checkboxinput form-check-input" value="{{ scope_id }}">
|
||||||
{{ scope_desc }}
|
{{ scope_desc }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<p id="url-{{ app.name.lower }}">
|
<p id="url-{{ app.name|slugify }}">
|
||||||
<a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code" target="_blank">
|
<a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code" target="_blank">
|
||||||
{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code
|
{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code
|
||||||
</a>
|
</a>
|
||||||
@ -51,11 +51,10 @@
|
|||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script>
|
<script>
|
||||||
{% for app in scopes.keys %}
|
{% for app in scopes.keys %}
|
||||||
let elements = document.getElementsByName("scope-{{ app.name.lower }}");
|
for (let element of document.getElementsByName("scope-{{ app.name|slugify }}")) {
|
||||||
for (let element of elements) {
|
|
||||||
element.onchange = function (event) {
|
element.onchange = function (event) {
|
||||||
let scope = ""
|
let scope = ""
|
||||||
for (let element of elements) {
|
for (let element of document.getElementsByName("scope-{{ app.name|slugify }}")) {
|
||||||
if (element.checked) {
|
if (element.checked) {
|
||||||
scope += element.value + " "
|
scope += element.value + " "
|
||||||
}
|
}
|
||||||
@ -63,7 +62,7 @@
|
|||||||
|
|
||||||
scope = scope.substr(0, scope.length - 1)
|
scope = scope.substr(0, scope.length - 1)
|
||||||
|
|
||||||
document.getElementById("url-{{ app.name.lower }}").innerHTML = 'Scopes : ' + scope
|
document.getElementById("url-{{ app.name|slugify }}").innerHTML = 'Scopes : ' + scope
|
||||||
+ '<br><a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='+ scope.replaceAll(' ', '%20')
|
+ '<br><a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='+ scope.replaceAll(' ', '%20')
|
||||||
+ '" target="_blank">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='
|
+ '" target="_blank">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='
|
||||||
+ scope.replaceAll(' ', '%20') + '</a>'
|
+ scope.replaceAll(' ', '%20') + '</a>'
|
||||||
|
Submodule apps/scripts updated: 7a022b9407...86bc2d2698
18
apps/treasury/migrations/0004_auto_20211005_1544.py
Normal file
18
apps/treasury/migrations/0004_auto_20211005_1544.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.24 on 2021-10-05 13:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('treasury', '0003_auto_20210321_1034'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sogecredit',
|
||||||
|
name='transactions',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='_sogecredit_transactions_+', to='note.MembershipTransaction', verbose_name='membership transactions'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal file
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.28 on 2023-01-29 22:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('treasury', '0004_auto_20211005_1544'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='bde',
|
||||||
|
field=models.CharField(choices=[('TotalistSpies', 'Tota[list]Spies'), ('Saperlistpopette', 'Saper[list]popette'), ('Finalist', 'Fina[list]'), ('Listorique', '[List]orique'), ('Satellist', 'Satel[list]'), ('Monopolist', 'Monopo[list]'), ('Kataclist', 'Katac[list]')], default='TotalistSpies', max_length=32, verbose_name='BDE'),
|
||||||
|
),
|
||||||
|
]
|
@ -28,8 +28,9 @@ class Invoice(models.Model):
|
|||||||
|
|
||||||
bde = models.CharField(
|
bde = models.CharField(
|
||||||
max_length=32,
|
max_length=32,
|
||||||
default='Saperlistpopette',
|
default='TotalistSpies',
|
||||||
choices=(
|
choices=(
|
||||||
|
('TotalistSpies', 'Tota[list]Spies'),
|
||||||
('Saperlistpopette', 'Saper[list]popette'),
|
('Saperlistpopette', 'Saper[list]popette'),
|
||||||
('Finalist', 'Fina[list]'),
|
('Finalist', 'Fina[list]'),
|
||||||
('Listorique', '[List]orique'),
|
('Listorique', '[List]orique'),
|
||||||
@ -95,7 +96,7 @@ class Invoice(models.Model):
|
|||||||
products = self.products.all()
|
products = self.products.all()
|
||||||
|
|
||||||
self.place = "Gif-sur-Yvette"
|
self.place = "Gif-sur-Yvette"
|
||||||
self.my_name = "BDE ENS Cachan"
|
self.my_name = "BDE ENS Paris Saclay"
|
||||||
self.my_address_street = "4 avenue des Sciences"
|
self.my_address_street = "4 avenue des Sciences"
|
||||||
self.my_city = "91190 Gif-sur-Yvette"
|
self.my_city = "91190 Gif-sur-Yvette"
|
||||||
self.bank_code = 30003
|
self.bank_code = 30003
|
||||||
@ -310,8 +311,8 @@ class SogeCredit(models.Model):
|
|||||||
amount = sum(transaction.total for transaction in self.transactions.all())
|
amount = sum(transaction.total for transaction in self.transactions.all())
|
||||||
if 'wei' in settings.INSTALLED_APPS:
|
if 'wei' in settings.INSTALLED_APPS:
|
||||||
from wei.models import WEIMembership
|
from wei.models import WEIMembership
|
||||||
if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\
|
if not WEIMembership.objects\
|
||||||
.exists():
|
.filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists():
|
||||||
# 80 € for people that don't go to WEI
|
# 80 € for people that don't go to WEI
|
||||||
amount += 8000
|
amount += 8000
|
||||||
return amount
|
return amount
|
||||||
@ -329,17 +330,18 @@ class SogeCredit(models.Model):
|
|||||||
bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start)
|
bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start)
|
||||||
kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start)
|
kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start)
|
||||||
|
|
||||||
if bde_qs.exists():
|
## Soge do not pay BDE and kfet memberships this year (2022-2023)
|
||||||
m = bde_qs.get()
|
# if bde_qs.exists():
|
||||||
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
# m = bde_qs.get()
|
||||||
if m.transaction not in self.transactions.all():
|
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||||
self.transactions.add(m.transaction)
|
# if m.transaction not in self.transactions.all():
|
||||||
|
# self.transactions.add(m.transaction)
|
||||||
if kfet_qs.exists():
|
#
|
||||||
m = kfet_qs.get()
|
# if kfet_qs.exists():
|
||||||
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
# m = kfet_qs.get()
|
||||||
if m.transaction not in self.transactions.all():
|
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||||
self.transactions.add(m.transaction)
|
# if m.transaction not in self.transactions.all():
|
||||||
|
# self.transactions.add(m.transaction)
|
||||||
|
|
||||||
if 'wei' in settings.INSTALLED_APPS:
|
if 'wei' in settings.INSTALLED_APPS:
|
||||||
from wei.models import WEIClub
|
from wei.models import WEIClub
|
||||||
|
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -2,11 +2,11 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm
|
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm
|
||||||
from .wei2021 import WEISurvey2021
|
from .wei2022 import WEISurvey2022
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
|
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
|
||||||
]
|
]
|
||||||
|
|
||||||
CurrentSurvey = WEISurvey2021
|
CurrentSurvey = WEISurvey2022
|
||||||
|
296
apps/wei/forms/surveys/wei2022.py
Normal file
296
apps/wei/forms/surveys/wei2022.py
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import time
|
||||||
|
from functools import lru_cache
|
||||||
|
from random import Random
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
||||||
|
from ...models import WEIMembership
|
||||||
|
|
||||||
|
WORDS = [
|
||||||
|
'ABBA', 'After', 'Alcoolique anonyme', 'Ambiance festive', 'Années 2000', 'Apéro', 'Art',
|
||||||
|
'Baby foot billard biere pong', 'BBQ', 'Before', 'Bière pong', 'Bon enfant', 'Calme', 'Canapé',
|
||||||
|
'Chanson paillarde', 'Chanson populaire', 'Chartreuse', 'Cheerleader', 'Chill', 'Choré',
|
||||||
|
'Cinéma', 'Cocktail', 'Comédie musicle', 'Commercial', 'Copaing', 'Danse', 'Dancefloor',
|
||||||
|
'Electro', 'Fanfare', 'Gin tonic', 'Inclusif', 'Jazz', "Jeux d'alcool", 'Jeux de carte',
|
||||||
|
'Jeux de rôle', 'Jeux de société', 'JUL', 'Jus de fruit', 'Kfet', 'Kleptomanie assurée',
|
||||||
|
'LGBTQ+', 'Livre', 'Morning beer', 'Musique', 'NAPS', 'Paillettes', 'Pastis', 'Paté Hénaff',
|
||||||
|
'Peluche', 'Pena baiona', "Peu d'alcool", 'Pilier de bar', 'PMU', 'Poulpe', 'Punch', 'Rap',
|
||||||
|
'Réveil', 'Rock', 'Rugby', 'Sandwich', 'Serge', 'Shot', 'Sociable', 'Spectacle', 'Techno',
|
||||||
|
'Techno house', 'Thérapie Taxi', 'Tradition kchanaises', 'Troisième mi-temps', 'Turn up',
|
||||||
|
'Vodka', 'Vodka pomme', 'Volley', 'Vomi stratégique'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WEISurveyForm2022(forms.Form):
|
||||||
|
"""
|
||||||
|
Survey form for the year 2022.
|
||||||
|
Members choose 20 words, from which we calculate the best associated bus.
|
||||||
|
"""
|
||||||
|
|
||||||
|
word = forms.ChoiceField(
|
||||||
|
label=_("Choose a word:"),
|
||||||
|
widget=forms.RadioSelect(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_registration(self, registration):
|
||||||
|
"""
|
||||||
|
Filter the bus selector with the buses of the current WEI.
|
||||||
|
"""
|
||||||
|
information = WEISurveyInformation2022(registration)
|
||||||
|
if not information.seed:
|
||||||
|
information.seed = int(1000 * time.time())
|
||||||
|
information.save(registration)
|
||||||
|
registration._force_save = True
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
if self.data:
|
||||||
|
self.fields["word"].choices = [(w, w) for w in WORDS]
|
||||||
|
if self.is_valid():
|
||||||
|
return
|
||||||
|
|
||||||
|
rng = Random((information.step + 1) * information.seed)
|
||||||
|
|
||||||
|
words = None
|
||||||
|
|
||||||
|
buses = WEISurveyAlgorithm2022.get_buses()
|
||||||
|
informations = {bus: WEIBusInformation2022(bus) for bus in buses}
|
||||||
|
scores = sum((list(informations[bus].scores.values()) for bus in buses), [])
|
||||||
|
average_score = sum(scores) / len(scores)
|
||||||
|
|
||||||
|
preferred_words = {bus: [word for word in WORDS
|
||||||
|
if informations[bus].scores[word] >= average_score]
|
||||||
|
for bus in buses}
|
||||||
|
while words is None or len(set(words)) != len(words):
|
||||||
|
# Ensure that there is no the same word 2 times
|
||||||
|
words = [rng.choice(words) for _ignored2, words in preferred_words.items()]
|
||||||
|
rng.shuffle(words)
|
||||||
|
words = [(w, w) for w in words]
|
||||||
|
self.fields["word"].choices = words
|
||||||
|
|
||||||
|
|
||||||
|
class WEIBusInformation2022(WEIBusInformation):
|
||||||
|
"""
|
||||||
|
For each word, the bus has a score
|
||||||
|
"""
|
||||||
|
scores: dict
|
||||||
|
|
||||||
|
def __init__(self, bus):
|
||||||
|
self.scores = {}
|
||||||
|
for word in WORDS:
|
||||||
|
self.scores[word] = 0.0
|
||||||
|
super().__init__(bus)
|
||||||
|
|
||||||
|
|
||||||
|
class WEISurveyInformation2022(WEISurveyInformation):
|
||||||
|
"""
|
||||||
|
We store the id of the selected bus. We store only the name, but is not used in the selection:
|
||||||
|
that's only for humans that try to read data.
|
||||||
|
"""
|
||||||
|
# Random seed that is stored at the first time to ensure that words are generated only once
|
||||||
|
seed = 0
|
||||||
|
step = 0
|
||||||
|
|
||||||
|
def __init__(self, registration):
|
||||||
|
for i in range(1, 21):
|
||||||
|
setattr(self, "word" + str(i), None)
|
||||||
|
super().__init__(registration)
|
||||||
|
|
||||||
|
|
||||||
|
class WEISurvey2022(WEISurvey):
|
||||||
|
"""
|
||||||
|
Survey for the year 2022.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_year(cls):
|
||||||
|
return 2022
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_survey_information_class(cls):
|
||||||
|
return WEISurveyInformation2022
|
||||||
|
|
||||||
|
def get_form_class(self):
|
||||||
|
return WEISurveyForm2022
|
||||||
|
|
||||||
|
def update_form(self, form):
|
||||||
|
"""
|
||||||
|
Filter the bus selector with the buses of the WEI.
|
||||||
|
"""
|
||||||
|
form.set_registration(self.registration)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def form_valid(self, form):
|
||||||
|
word = form.cleaned_data["word"]
|
||||||
|
self.information.step += 1
|
||||||
|
setattr(self.information, "word" + str(self.information.step), word)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_algorithm_class(cls):
|
||||||
|
return WEISurveyAlgorithm2022
|
||||||
|
|
||||||
|
def is_complete(self) -> bool:
|
||||||
|
"""
|
||||||
|
The survey is complete once the bus is chosen.
|
||||||
|
"""
|
||||||
|
return self.information.step == 20
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@lru_cache()
|
||||||
|
def word_mean(cls, word):
|
||||||
|
"""
|
||||||
|
Calculate the mid-score given by all buses.
|
||||||
|
"""
|
||||||
|
buses = cls.get_algorithm_class().get_buses()
|
||||||
|
return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count()
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def score(self, bus):
|
||||||
|
if not self.is_complete():
|
||||||
|
raise ValueError("Survey is not ended, can't calculate score")
|
||||||
|
|
||||||
|
bus_info = self.get_algorithm_class().get_bus_information(bus)
|
||||||
|
# Score is the given score by the bus subtracted to the mid-score of the buses.
|
||||||
|
s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))]
|
||||||
|
- self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20
|
||||||
|
return s
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def scores_per_bus(self):
|
||||||
|
return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()}
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def ordered_buses(self):
|
||||||
|
values = list(self.scores_per_bus().items())
|
||||||
|
values.sort(key=lambda item: -item[1])
|
||||||
|
return values
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clear_cache(cls):
|
||||||
|
cls.word_mean.cache_clear()
|
||||||
|
return super().clear_cache()
|
||||||
|
|
||||||
|
|
||||||
|
class WEISurveyAlgorithm2022(WEISurveyAlgorithm):
|
||||||
|
"""
|
||||||
|
The algorithm class for the year 2022.
|
||||||
|
We use Gale-Shapley algorithm to attribute 1y students into buses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_survey_class(cls):
|
||||||
|
return WEISurvey2022
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bus_information_class(cls):
|
||||||
|
return WEIBusInformation2022
|
||||||
|
|
||||||
|
def run_algorithm(self, display_tqdm=False):
|
||||||
|
"""
|
||||||
|
Gale-Shapley algorithm implementation.
|
||||||
|
We modify it to allow buses to have multiple "weddings".
|
||||||
|
"""
|
||||||
|
surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys
|
||||||
|
surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys
|
||||||
|
# Don't manage hardcoded people
|
||||||
|
surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
|
||||||
|
|
||||||
|
# Reset previous algorithm run
|
||||||
|
for survey in surveys:
|
||||||
|
survey.free()
|
||||||
|
survey.save()
|
||||||
|
|
||||||
|
non_men = [s for s in surveys if s.registration.gender != 'male']
|
||||||
|
men = [s for s in surveys if s.registration.gender == 'male']
|
||||||
|
|
||||||
|
quotas = {}
|
||||||
|
registrations = self.get_registrations()
|
||||||
|
non_men_total = registrations.filter(~Q(gender='male')).count()
|
||||||
|
for bus in self.get_buses():
|
||||||
|
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||||
|
# Remove hardcoded people
|
||||||
|
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||||
|
registration__information_json__icontains="hardcoded").count()
|
||||||
|
quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats)
|
||||||
|
|
||||||
|
tqdm_obj = None
|
||||||
|
if display_tqdm:
|
||||||
|
from tqdm import tqdm
|
||||||
|
tqdm_obj = tqdm(total=len(non_men), desc="Non-hommes")
|
||||||
|
|
||||||
|
# Repartition for non men people first
|
||||||
|
self.make_repartition(non_men, quotas, tqdm_obj=tqdm_obj)
|
||||||
|
|
||||||
|
quotas = {}
|
||||||
|
for bus in self.get_buses():
|
||||||
|
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||||
|
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
|
||||||
|
# Remove hardcoded people
|
||||||
|
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||||
|
registration__information_json__icontains="hardcoded").count()
|
||||||
|
quotas[bus] = free_seats
|
||||||
|
|
||||||
|
if display_tqdm:
|
||||||
|
tqdm_obj.close()
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
tqdm_obj = tqdm(total=len(men), desc="Hommes")
|
||||||
|
|
||||||
|
self.make_repartition(men, quotas, tqdm_obj=tqdm_obj)
|
||||||
|
|
||||||
|
if display_tqdm:
|
||||||
|
tqdm_obj.close()
|
||||||
|
|
||||||
|
# Clear cache information after running algorithm
|
||||||
|
WEISurvey2022.clear_cache()
|
||||||
|
|
||||||
|
def make_repartition(self, surveys, quotas=None, tqdm_obj=None):
|
||||||
|
free_surveys = surveys.copy() # Remaining surveys
|
||||||
|
while free_surveys: # Some students are not affected
|
||||||
|
survey = free_surveys[0]
|
||||||
|
buses = survey.ordered_buses() # Preferences of the student
|
||||||
|
for bus, current_score in buses:
|
||||||
|
if self.get_bus_information(bus).has_free_seats(surveys, quotas):
|
||||||
|
# Selected bus has free places. Put student in the bus
|
||||||
|
survey.select_bus(bus)
|
||||||
|
survey.save()
|
||||||
|
free_surveys.remove(survey)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Current bus has not enough places. Remove the least preferred student from the bus if existing
|
||||||
|
least_preferred_survey = None
|
||||||
|
least_score = -1
|
||||||
|
# Find the least student in the bus that has a lower score than the current student
|
||||||
|
for survey2 in surveys:
|
||||||
|
if not survey2.information.valid or survey2.information.get_selected_bus() != bus:
|
||||||
|
continue
|
||||||
|
score2 = survey2.score(bus)
|
||||||
|
if current_score <= score2: # Ignore better students
|
||||||
|
continue
|
||||||
|
if least_preferred_survey is None or score2 < least_score:
|
||||||
|
least_preferred_survey = survey2
|
||||||
|
least_score = score2
|
||||||
|
|
||||||
|
if least_preferred_survey is not None:
|
||||||
|
# Remove the least student from the bus and put the current student in.
|
||||||
|
# If it does not exist, choose the next bus.
|
||||||
|
least_preferred_survey.free()
|
||||||
|
least_preferred_survey.save()
|
||||||
|
free_surveys.append(least_preferred_survey)
|
||||||
|
survey.select_bus(bus)
|
||||||
|
survey.save()
|
||||||
|
free_surveys.remove(survey)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError(f"User {survey.registration.user} has no free seat")
|
||||||
|
|
||||||
|
if tqdm_obj is not None:
|
||||||
|
tqdm_obj.n = len(surveys) - len(free_surveys)
|
||||||
|
tqdm_obj.refresh()
|
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal file
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.26 on 2022-09-04 21:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wei', '0003_bus_size'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weiclub',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveIntegerField(default=2022, unique=True, verbose_name='year'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal file
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.28 on 2023-01-28 17:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wei', '0004_auto_20220904_2325'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weiclub',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveIntegerField(default=2023, unique=True, verbose_name='year'),
|
||||||
|
),
|
||||||
|
]
|
@ -25,6 +25,7 @@ class TestWEIAlgorithm(TestCase):
|
|||||||
email="wei2021@example.com",
|
email="wei2021@example.com",
|
||||||
date_start='2021-09-17',
|
date_start='2021-09-17',
|
||||||
date_end='2021-09-19',
|
date_end='2021-09-19',
|
||||||
|
year=2021,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.buses = []
|
self.buses = []
|
||||||
|
110
apps/wei/tests/test_wei_algorithm_2022.py
Normal file
110
apps/wei/tests/test_wei_algorithm_2022.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ..forms.surveys.wei2022 import WEIBusInformation2022, WEISurvey2022, WORDS, WEISurveyInformation2022
|
||||||
|
from ..models import Bus, WEIClub, WEIRegistration
|
||||||
|
|
||||||
|
|
||||||
|
class TestWEIAlgorithm(TestCase):
|
||||||
|
"""
|
||||||
|
Run some tests to ensure that the WEI algorithm is working well.
|
||||||
|
"""
|
||||||
|
fixtures = ('initial',)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Create some test data, with one WEI and 10 buses with random score attributions.
|
||||||
|
"""
|
||||||
|
self.wei = WEIClub.objects.create(
|
||||||
|
name="WEI 2022",
|
||||||
|
email="wei2022@example.com",
|
||||||
|
date_start='2022-09-16',
|
||||||
|
date_end='2022-09-18',
|
||||||
|
year=2022,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.buses = []
|
||||||
|
for i in range(10):
|
||||||
|
bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
|
||||||
|
self.buses.append(bus)
|
||||||
|
information = WEIBusInformation2022(bus)
|
||||||
|
for word in WORDS:
|
||||||
|
information.scores[word] = random.randint(0, 101)
|
||||||
|
information.save()
|
||||||
|
bus.save()
|
||||||
|
|
||||||
|
def test_survey_algorithm_small(self):
|
||||||
|
"""
|
||||||
|
There are only a few people in each bus, ensure that each person has its best bus
|
||||||
|
"""
|
||||||
|
# Add a few users
|
||||||
|
for i in range(10):
|
||||||
|
user = User.objects.create(username=f"user{i}")
|
||||||
|
registration = WEIRegistration.objects.create(
|
||||||
|
user=user,
|
||||||
|
wei=self.wei,
|
||||||
|
first_year=True,
|
||||||
|
birth_date='2000-01-01',
|
||||||
|
)
|
||||||
|
information = WEISurveyInformation2022(registration)
|
||||||
|
for j in range(1, 21):
|
||||||
|
setattr(information, f'word{j}', random.choice(WORDS))
|
||||||
|
information.step = 20
|
||||||
|
information.save(registration)
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
# Run algorithm
|
||||||
|
WEISurvey2022.get_algorithm_class()().run_algorithm()
|
||||||
|
|
||||||
|
# Ensure that everyone has its first choice
|
||||||
|
for r in WEIRegistration.objects.filter(wei=self.wei).all():
|
||||||
|
survey = WEISurvey2022(r)
|
||||||
|
preferred_bus = survey.ordered_buses()[0][0]
|
||||||
|
chosen_bus = survey.information.get_selected_bus()
|
||||||
|
self.assertEqual(preferred_bus, chosen_bus)
|
||||||
|
|
||||||
|
def test_survey_algorithm_full(self):
|
||||||
|
"""
|
||||||
|
Buses are full of first year people, ensure that they are happy
|
||||||
|
"""
|
||||||
|
# Add a lot of users
|
||||||
|
for i in range(95):
|
||||||
|
user = User.objects.create(username=f"user{i}")
|
||||||
|
registration = WEIRegistration.objects.create(
|
||||||
|
user=user,
|
||||||
|
wei=self.wei,
|
||||||
|
first_year=True,
|
||||||
|
birth_date='2000-01-01',
|
||||||
|
)
|
||||||
|
information = WEISurveyInformation2022(registration)
|
||||||
|
for j in range(1, 21):
|
||||||
|
setattr(information, f'word{j}', random.choice(WORDS))
|
||||||
|
information.step = 20
|
||||||
|
information.save(registration)
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
# Run algorithm
|
||||||
|
WEISurvey2022.get_algorithm_class()().run_algorithm()
|
||||||
|
|
||||||
|
penalty = 0
|
||||||
|
# Ensure that everyone seems to be happy
|
||||||
|
# We attribute a penalty for each user that didn't have its first choice
|
||||||
|
# The penalty is the square of the distance between the score of the preferred bus
|
||||||
|
# and the score of the attributed bus
|
||||||
|
# We consider it acceptable if the mean of this distance is lower than 5 %
|
||||||
|
for r in WEIRegistration.objects.filter(wei=self.wei).all():
|
||||||
|
survey = WEISurvey2022(r)
|
||||||
|
chosen_bus = survey.information.get_selected_bus()
|
||||||
|
buses = survey.ordered_buses()
|
||||||
|
score = min(v for bus, v in buses if bus == chosen_bus)
|
||||||
|
max_score = buses[0][1]
|
||||||
|
penalty += (max_score - score) ** 2
|
||||||
|
|
||||||
|
self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance
|
||||||
|
|
||||||
|
self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 %
|
@ -782,7 +782,7 @@ class TestDefaultWEISurvey(TestCase):
|
|||||||
WEISurvey.update_form(None, None)
|
WEISurvey.update_form(None, None)
|
||||||
|
|
||||||
self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey)
|
self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey)
|
||||||
self.assertEqual(CurrentSurvey.get_year(), 2021)
|
self.assertEqual(CurrentSurvey.get_year(), 2022)
|
||||||
|
|
||||||
|
|
||||||
class TestWeiAPI(TestAPI):
|
class TestWeiAPI(TestAPI):
|
||||||
|
@ -86,7 +86,7 @@ Génération
|
|||||||
|
|
||||||
Les factures peuvent s'exporter au format PDF (là est tout leur intérêt). Pour cela, on utilise le template LaTeX
|
Les factures peuvent s'exporter au format PDF (là est tout leur intérêt). Pour cela, on utilise le template LaTeX
|
||||||
présent à l'adresse suivante :
|
présent à l'adresse suivante :
|
||||||
`/templates/treasury/invoice_sample.tex <https://gitlab.crans.org/bde/nk20/-/tree/master/templates/treasury/invoice_sample.tex>`_
|
`/templates/treasury/invoice_sample.tex <https://gitlab.crans.org/bde/nk20/-/tree/main/templates/treasury/invoice_sample.tex>`_
|
||||||
|
|
||||||
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.
|
||||||
|
@ -41,8 +41,14 @@ On a ensuite besoin de définir nos propres scopes afin d'avoir des permissions
|
|||||||
|
|
||||||
OAUTH2_PROVIDER = {
|
OAUTH2_PROVIDER = {
|
||||||
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
|
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
|
||||||
|
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
|
||||||
|
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cela a pour effet d'avoir des scopes sous la forme ``PERMISSION_CLUB``,
|
||||||
|
et de demander des scopes facultatives (voir plus bas).
|
||||||
|
Un jeton de rafraîchissement expire de plus au bout de 14 jours, si non-renouvelé.
|
||||||
|
|
||||||
On ajoute enfin les routes dans ``urls.py`` :
|
On ajoute enfin les routes dans ``urls.py`` :
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
@ -94,6 +100,27 @@ du format renvoyé.
|
|||||||
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.
|
||||||
|
|
||||||
|
.. danger::
|
||||||
|
|
||||||
|
Demander des scopes n'implique pas de les avoir.
|
||||||
|
|
||||||
|
Lorsque des scopes sont demandées par un client, la Note
|
||||||
|
va considérer l'ensemble des permissions accessibles parmi
|
||||||
|
ce qui est demandé. Dans vos programmes, vous devrez donc
|
||||||
|
vérifier les permissions acquises (communiquées lors de la
|
||||||
|
récupération du jeton d'accès à partir du grant code),
|
||||||
|
et prévoir un comportement dans le cas où des permissions
|
||||||
|
sont manquantes.
|
||||||
|
|
||||||
|
Cela offre un intérêt supérieur par rapport au protocole
|
||||||
|
OAuth2 classique, consistant à demander trop de permissions
|
||||||
|
et agir en conséquence.
|
||||||
|
|
||||||
|
Par exemple, vous pourriez demander la permission d'accéder
|
||||||
|
aux membres d'un club ou de faire des transactions, et agir
|
||||||
|
uniquement dans le cas où l'utilisateur connecté possède la
|
||||||
|
permission problématique.
|
||||||
|
|
||||||
Avec Django-allauth
|
Avec Django-allauth
|
||||||
###################
|
###################
|
||||||
|
|
||||||
@ -116,6 +143,7 @@ installées (sur votre propre client), puis de bien ajouter l'application social
|
|||||||
SOCIALACCOUNT_PROVIDERS = {
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
'notekfet': {
|
'notekfet': {
|
||||||
# 'DOMAIN': 'note.crans.org',
|
# 'DOMAIN': 'note.crans.org',
|
||||||
|
'SCOPE': ['1_1', '2_1'],
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@ -123,6 +151,10 @@ installées (sur votre propre client), puis de bien ajouter l'application social
|
|||||||
Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il
|
Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il
|
||||||
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.
|
||||||
|
Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur
|
||||||
|
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
|
||||||
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
|
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
|
||||||
à adapter selon votre configuration.
|
à adapter selon votre configuration.
|
||||||
|
@ -88,7 +88,7 @@ On clone donc le dépôt en tant que ``www-data`` :
|
|||||||
|
|
||||||
$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git /var/www/note_kfet
|
$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git /var/www/note_kfet
|
||||||
|
|
||||||
Par défaut, le dépôt est configuré pour suivre la branche ``master``, qui est la branche
|
Par défaut, le dépôt est configuré pour suivre la branche ``main``, qui est la branche
|
||||||
stable, notamment installée sur `<https://note.crans.org/>`_. Pour changer de branche,
|
stable, notamment installée sur `<https://note.crans.org/>`_. Pour changer de branche,
|
||||||
notamment passer sur la branche ``beta`` sur un serveur de pré-production (un peu comme
|
notamment passer sur la branche ``beta`` sur un serveur de pré-production (un peu comme
|
||||||
`<https://note-dev.crans.org/>`_), on peut faire :
|
`<https://note-dev.crans.org/>`_), on peut faire :
|
||||||
@ -587,7 +587,7 @@ Dans ce fichier, remplissez :
|
|||||||
---
|
---
|
||||||
note:
|
note:
|
||||||
server_name: note.crans.org
|
server_name: note.crans.org
|
||||||
git_branch: master
|
git_branch: main
|
||||||
cron_enabled: true
|
cron_enabled: true
|
||||||
email: notekfet2020@lists.crans.org
|
email: notekfet2020@lists.crans.org
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,16 +7,16 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-09-28 17:02+0200\n"
|
"POT-Creation-Date: 2022-04-10 22:34+0200\n"
|
||||||
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
|
||||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||||
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
|
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 4.3.2\n"
|
"X-Generator: Poedit 3.0\n"
|
||||||
|
|
||||||
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
||||||
#: apps/activity/models.py:167
|
#: apps/activity/models.py:167
|
||||||
@ -56,7 +56,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
|
|||||||
#: apps/member/models.py:199
|
#: apps/member/models.py:199
|
||||||
#: apps/member/templates/member/includes/club_info.html:4
|
#: apps/member/templates/member/includes/club_info.html:4
|
||||||
#: apps/member/templates/member/includes/profile_info.html:4
|
#: apps/member/templates/member/includes/profile_info.html:4
|
||||||
#: apps/note/models/notes.py:231 apps/note/models/transactions.py:26
|
#: apps/note/models/notes.py:263 apps/note/models/transactions.py:26
|
||||||
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301
|
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301
|
||||||
#: apps/permission/models.py:330
|
#: apps/permission/models.py:330
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:16
|
#: apps/registration/templates/registration/future_profile_detail.html:16
|
||||||
@ -114,7 +114,7 @@ msgstr "Lieu où l'activité est organisée, par exemple la Kfet."
|
|||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr "type"
|
msgstr "type"
|
||||||
|
|
||||||
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:305
|
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:307
|
||||||
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
|
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
|
||||||
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
|
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
|
||||||
#: apps/wei/templates/wei/survey.html:15
|
#: apps/wei/templates/wei/survey.html:15
|
||||||
@ -279,7 +279,7 @@ msgstr "Prénom"
|
|||||||
msgid "Note"
|
msgid "Note"
|
||||||
msgstr "Note"
|
msgstr "Note"
|
||||||
|
|
||||||
#: apps/activity/tables.py:90 apps/member/tables.py:49
|
#: apps/activity/tables.py:90 apps/member/tables.py:50
|
||||||
msgid "Balance"
|
msgid "Balance"
|
||||||
msgstr "Solde du compte"
|
msgstr "Solde du compte"
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ msgstr "Invité supprimé"
|
|||||||
#: apps/note/models/transactions.py:257
|
#: apps/note/models/transactions.py:257
|
||||||
#: apps/note/templates/note/transaction_form.html:17
|
#: apps/note/templates/note/transaction_form.html:17
|
||||||
#: apps/note/templates/note/transaction_form.html:152
|
#: apps/note/templates/note/transaction_form.html:152
|
||||||
#: note_kfet/templates/base.html:73
|
#: note_kfet/templates/base.html:72
|
||||||
msgid "Transfer"
|
msgid "Transfer"
|
||||||
msgstr "Virement"
|
msgstr "Virement"
|
||||||
|
|
||||||
@ -320,13 +320,13 @@ msgstr "Entrées"
|
|||||||
msgid "Return to activity page"
|
msgid "Return to activity page"
|
||||||
msgstr "Retour à la page de l'activité"
|
msgstr "Retour à la page de l'activité"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_entry.html:89
|
#: apps/activity/templates/activity/activity_entry.html:94
|
||||||
#: apps/activity/templates/activity/activity_entry.html:124
|
#: apps/activity/templates/activity/activity_entry.html:129
|
||||||
msgid "Entry done, but caution: the user is not a Kfet member."
|
msgid "Entry done, but caution: the user is not a Kfet member."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Entrée effectuée, mais attention : la personne n'est pas un adhérent Kfet."
|
"Entrée effectuée, mais attention : la personne n'est pas un adhérent Kfet."
|
||||||
|
|
||||||
#: apps/activity/templates/activity/activity_entry.html:127
|
#: apps/activity/templates/activity/activity_entry.html:132
|
||||||
msgid "Entry done!"
|
msgid "Entry done!"
|
||||||
msgstr "Entrée effectuée !"
|
msgstr "Entrée effectuée !"
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ msgid "validate"
|
|||||||
msgstr "valider"
|
msgstr "valider"
|
||||||
|
|
||||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||||
#: apps/logs/models.py:64 apps/note/tables.py:195
|
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||||
msgid "edit"
|
msgid "edit"
|
||||||
msgstr "modifier"
|
msgstr "modifier"
|
||||||
|
|
||||||
@ -400,37 +400,37 @@ msgstr "Inviter"
|
|||||||
msgid "Create new activity"
|
msgid "Create new activity"
|
||||||
msgstr "Créer une nouvelle activité"
|
msgstr "Créer une nouvelle activité"
|
||||||
|
|
||||||
#: apps/activity/views.py:67 note_kfet/templates/base.html:91
|
#: apps/activity/views.py:67 note_kfet/templates/base.html:90
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr "Activités"
|
msgstr "Activités"
|
||||||
|
|
||||||
#: apps/activity/views.py:95
|
#: apps/activity/views.py:93
|
||||||
msgid "Activity detail"
|
msgid "Activity detail"
|
||||||
msgstr "Détails de l'activité"
|
msgstr "Détails de l'activité"
|
||||||
|
|
||||||
#: apps/activity/views.py:115
|
#: apps/activity/views.py:113
|
||||||
msgid "Update activity"
|
msgid "Update activity"
|
||||||
msgstr "Modifier l'activité"
|
msgstr "Modifier l'activité"
|
||||||
|
|
||||||
#: apps/activity/views.py:142
|
#: apps/activity/views.py:140
|
||||||
msgid "Invite guest to the activity \"{}\""
|
msgid "Invite guest to the activity \"{}\""
|
||||||
msgstr "Invitation pour l'activité « {} »"
|
msgstr "Invitation pour l'activité « {} »"
|
||||||
|
|
||||||
#: apps/activity/views.py:177
|
#: apps/activity/views.py:178
|
||||||
msgid "You are not allowed to display the entry interface for this activity."
|
msgid "You are not allowed to display the entry interface for this activity."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'êtes pas autorisé à afficher l'interface des entrées pour cette "
|
"Vous n'êtes pas autorisé à afficher l'interface des entrées pour cette "
|
||||||
"activité."
|
"activité."
|
||||||
|
|
||||||
#: apps/activity/views.py:180
|
#: apps/activity/views.py:181
|
||||||
msgid "This activity does not support activity entries."
|
msgid "This activity does not support activity entries."
|
||||||
msgstr "Cette activité ne requiert pas d'entrées."
|
msgstr "Cette activité ne requiert pas d'entrées."
|
||||||
|
|
||||||
#: apps/activity/views.py:183
|
#: apps/activity/views.py:184
|
||||||
msgid "This activity is closed."
|
msgid "This activity is closed."
|
||||||
msgstr "Cette activité est fermée."
|
msgstr "Cette activité est fermée."
|
||||||
|
|
||||||
#: apps/activity/views.py:279
|
#: apps/activity/views.py:280
|
||||||
msgid "Entry for activity \"{}\""
|
msgid "Entry for activity \"{}\""
|
||||||
msgstr "Entrées pour l'activité « {} »"
|
msgstr "Entrées pour l'activité « {} »"
|
||||||
|
|
||||||
@ -466,9 +466,9 @@ msgstr "nouvelles données"
|
|||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr "créer"
|
msgstr "créer"
|
||||||
|
|
||||||
#: apps/logs/models.py:65 apps/note/tables.py:165 apps/note/tables.py:201
|
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||||
#: apps/wei/tables.py:74
|
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||||
msgid "delete"
|
msgid "delete"
|
||||||
msgstr "supprimer"
|
msgstr "supprimer"
|
||||||
|
|
||||||
@ -507,11 +507,11 @@ msgstr "cotisation pour adhérer (normalien élève)"
|
|||||||
msgid "membership fee (unpaid students)"
|
msgid "membership fee (unpaid students)"
|
||||||
msgstr "cotisation pour adhérer (normalien étudiant)"
|
msgstr "cotisation pour adhérer (normalien étudiant)"
|
||||||
|
|
||||||
#: apps/member/admin.py:65 apps/member/models.py:317
|
#: apps/member/admin.py:65 apps/member/models.py:319
|
||||||
msgid "roles"
|
msgid "roles"
|
||||||
msgstr "rôles"
|
msgstr "rôles"
|
||||||
|
|
||||||
#: apps/member/admin.py:66 apps/member/models.py:331
|
#: apps/member/admin.py:66 apps/member/models.py:333
|
||||||
msgid "fee"
|
msgid "fee"
|
||||||
msgstr "cotisation"
|
msgstr "cotisation"
|
||||||
|
|
||||||
@ -547,7 +547,7 @@ msgstr "Taille maximale : 2 Mo"
|
|||||||
msgid "This image cannot be loaded."
|
msgid "This image cannot be loaded."
|
||||||
msgstr "Cette image ne peut pas être chargée."
|
msgstr "Cette image ne peut pas être chargée."
|
||||||
|
|
||||||
#: apps/member/forms.py:141 apps/member/views.py:102
|
#: apps/member/forms.py:141 apps/member/views.py:103
|
||||||
#: apps/registration/forms.py:33 apps/registration/views.py:262
|
#: apps/registration/forms.py:33 apps/registration/views.py:262
|
||||||
msgid "An alias with a similar name already exists."
|
msgid "An alias with a similar name already exists."
|
||||||
msgstr "Un alias avec un nom similaire existe déjà."
|
msgstr "Un alias avec un nom similaire existe déjà."
|
||||||
@ -610,14 +610,14 @@ msgid "hash"
|
|||||||
msgstr "haché"
|
msgstr "haché"
|
||||||
|
|
||||||
#: apps/member/models.py:38
|
#: apps/member/models.py:38
|
||||||
#: apps/member/templates/member/includes/profile_info.html:35
|
#: apps/member/templates/member/includes/profile_info.html:43
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:40
|
#: apps/registration/templates/registration/future_profile_detail.html:40
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:44
|
#: apps/wei/templates/wei/weimembership_form.html:44
|
||||||
msgid "phone number"
|
msgid "phone number"
|
||||||
msgstr "numéro de téléphone"
|
msgstr "numéro de téléphone"
|
||||||
|
|
||||||
#: apps/member/models.py:45
|
#: apps/member/models.py:45
|
||||||
#: apps/member/templates/member/includes/profile_info.html:29
|
#: apps/member/templates/member/includes/profile_info.html:37
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:34
|
#: apps/registration/templates/registration/future_profile_detail.html:34
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:38
|
#: apps/wei/templates/wei/weimembership_form.html:38
|
||||||
msgid "section"
|
msgid "section"
|
||||||
@ -705,14 +705,14 @@ msgid "Year of entry to the school (None if not ENS student)"
|
|||||||
msgstr "Année d'entrée dans l'école (None si non-étudiant·e de l'ENS)"
|
msgstr "Année d'entrée dans l'école (None si non-étudiant·e de l'ENS)"
|
||||||
|
|
||||||
#: apps/member/models.py:83
|
#: apps/member/models.py:83
|
||||||
#: apps/member/templates/member/includes/profile_info.html:39
|
#: apps/member/templates/member/includes/profile_info.html:47
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:37
|
#: apps/registration/templates/registration/future_profile_detail.html:37
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:41
|
#: apps/wei/templates/wei/weimembership_form.html:41
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr "adresse"
|
msgstr "adresse"
|
||||||
|
|
||||||
#: apps/member/models.py:90
|
#: apps/member/models.py:90
|
||||||
#: apps/member/templates/member/includes/profile_info.html:42
|
#: apps/member/templates/member/includes/profile_info.html:50
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:43
|
#: apps/registration/templates/registration/future_profile_detail.html:43
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:47
|
#: apps/wei/templates/wei/weimembership_form.html:47
|
||||||
msgid "paid"
|
msgid "paid"
|
||||||
@ -784,7 +784,7 @@ msgstr "Activez votre compte Note Kfet"
|
|||||||
|
|
||||||
#: apps/member/models.py:204
|
#: apps/member/models.py:204
|
||||||
#: apps/member/templates/member/includes/club_info.html:55
|
#: apps/member/templates/member/includes/club_info.html:55
|
||||||
#: apps/member/templates/member/includes/profile_info.html:32
|
#: apps/member/templates/member/includes/profile_info.html:40
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:22
|
#: apps/registration/templates/registration/future_profile_detail.html:22
|
||||||
#: apps/wei/templates/wei/base.html:70
|
#: apps/wei/templates/wei/base.html:70
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:20
|
#: apps/wei/templates/wei/weimembership_form.html:20
|
||||||
@ -833,50 +833,50 @@ msgstr ""
|
|||||||
"Date maximale d'une fin d'adhésion, après laquelle les adhérents doivent la "
|
"Date maximale d'une fin d'adhésion, après laquelle les adhérents doivent la "
|
||||||
"renouveler."
|
"renouveler."
|
||||||
|
|
||||||
#: apps/member/models.py:286 apps/member/models.py:311
|
#: apps/member/models.py:288 apps/member/models.py:313
|
||||||
#: apps/note/models/notes.py:176
|
#: apps/note/models/notes.py:176
|
||||||
msgid "club"
|
msgid "club"
|
||||||
msgstr "club"
|
msgstr "club"
|
||||||
|
|
||||||
#: apps/member/models.py:287
|
#: apps/member/models.py:289
|
||||||
msgid "clubs"
|
msgid "clubs"
|
||||||
msgstr "clubs"
|
msgstr "clubs"
|
||||||
|
|
||||||
#: apps/member/models.py:322
|
#: apps/member/models.py:324
|
||||||
msgid "membership starts on"
|
msgid "membership starts on"
|
||||||
msgstr "l'adhésion commence le"
|
msgstr "l'adhésion commence le"
|
||||||
|
|
||||||
#: apps/member/models.py:326
|
#: apps/member/models.py:328
|
||||||
msgid "membership ends on"
|
msgid "membership ends on"
|
||||||
msgstr "l'adhésion finit le"
|
msgstr "l'adhésion finit le"
|
||||||
|
|
||||||
#: apps/member/models.py:422
|
#: apps/member/models.py:430
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "The role {role} does not apply to the club {club}."
|
msgid "The role {role} does not apply to the club {club}."
|
||||||
msgstr "Le rôle {role} ne s'applique pas au club {club}."
|
msgstr "Le rôle {role} ne s'applique pas au club {club}."
|
||||||
|
|
||||||
#: apps/member/models.py:431 apps/member/views.py:651
|
#: apps/member/models.py:439 apps/member/views.py:712
|
||||||
msgid "User is already a member of the club"
|
msgid "User is already a member of the club"
|
||||||
msgstr "L'utilisateur est déjà membre du club"
|
msgstr "L'utilisateur est déjà membre du club"
|
||||||
|
|
||||||
#: apps/member/models.py:443 apps/member/views.py:660
|
#: apps/member/models.py:451 apps/member/views.py:721
|
||||||
msgid "User is not a member of the parent club"
|
msgid "User is not a member of the parent club"
|
||||||
msgstr "L'utilisateur n'est pas membre du club parent"
|
msgstr "L'utilisateur n'est pas membre du club parent"
|
||||||
|
|
||||||
#: apps/member/models.py:496
|
#: apps/member/models.py:504
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Membership of {user} for the club {club}"
|
msgid "Membership of {user} for the club {club}"
|
||||||
msgstr "Adhésion de {user} pour le club {club}"
|
msgstr "Adhésion de {user} pour le club {club}"
|
||||||
|
|
||||||
#: apps/member/models.py:499 apps/note/models/transactions.py:389
|
#: apps/member/models.py:507 apps/note/models/transactions.py:389
|
||||||
msgid "membership"
|
msgid "membership"
|
||||||
msgstr "adhésion"
|
msgstr "adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:500
|
#: apps/member/models.py:508
|
||||||
msgid "memberships"
|
msgid "memberships"
|
||||||
msgstr "adhésions"
|
msgstr "adhésions"
|
||||||
|
|
||||||
#: apps/member/tables.py:137
|
#: apps/member/tables.py:139
|
||||||
msgid "Renew"
|
msgid "Renew"
|
||||||
msgstr "Renouveler"
|
msgstr "Renouveler"
|
||||||
|
|
||||||
@ -924,7 +924,7 @@ msgid "Account #"
|
|||||||
msgstr "Compte n°"
|
msgstr "Compte n°"
|
||||||
|
|
||||||
#: apps/member/templates/member/base.html:48
|
#: apps/member/templates/member/base.html:48
|
||||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||||
msgid "Update Profile"
|
msgid "Update Profile"
|
||||||
@ -985,13 +985,14 @@ msgstr ""
|
|||||||
"seront à nouveau possible."
|
"seront à nouveau possible."
|
||||||
|
|
||||||
#: apps/member/templates/member/club_alias.html:10
|
#: apps/member/templates/member/club_alias.html:10
|
||||||
#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:253
|
#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:287
|
||||||
#: apps/member/views.py:456
|
#: apps/member/views.py:517
|
||||||
msgid "Note aliases"
|
msgid "Note aliases"
|
||||||
msgstr "Alias de la note"
|
msgstr "Alias de la note"
|
||||||
|
|
||||||
#: apps/member/templates/member/club_alias.html:20
|
#: apps/member/templates/member/club_alias.html:20
|
||||||
#: apps/member/templates/member/profile_alias.html:19
|
#: apps/member/templates/member/profile_alias.html:19
|
||||||
|
#: apps/member/templates/member/profile_trust.html:19
|
||||||
#: apps/treasury/tables.py:99
|
#: apps/treasury/tables.py:99
|
||||||
#: apps/treasury/templates/treasury/sogecredit_list.html:34
|
#: apps/treasury/templates/treasury/sogecredit_list.html:34
|
||||||
#: apps/treasury/templates/treasury/sogecredit_list.html:73
|
#: apps/treasury/templates/treasury/sogecredit_list.html:73
|
||||||
@ -1044,7 +1045,7 @@ msgid "membership fee"
|
|||||||
msgstr "cotisation pour adhérer"
|
msgstr "cotisation pour adhérer"
|
||||||
|
|
||||||
#: apps/member/templates/member/includes/club_info.html:43
|
#: apps/member/templates/member/includes/club_info.html:43
|
||||||
#: apps/member/templates/member/includes/profile_info.html:47
|
#: apps/member/templates/member/includes/profile_info.html:55
|
||||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:24
|
#: apps/treasury/templates/treasury/sogecredit_detail.html:24
|
||||||
#: apps/wei/templates/wei/base.html:60
|
#: apps/wei/templates/wei/base.html:60
|
||||||
msgid "balance"
|
msgid "balance"
|
||||||
@ -1052,7 +1053,7 @@ msgstr "solde du compte"
|
|||||||
|
|
||||||
#: apps/member/templates/member/includes/club_info.html:47
|
#: apps/member/templates/member/includes/club_info.html:47
|
||||||
#: apps/member/templates/member/includes/profile_info.html:20
|
#: apps/member/templates/member/includes/profile_info.html:20
|
||||||
#: apps/note/models/notes.py:255 apps/wei/templates/wei/base.html:66
|
#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:66
|
||||||
msgid "aliases"
|
msgid "aliases"
|
||||||
msgstr "alias"
|
msgstr "alias"
|
||||||
|
|
||||||
@ -1076,7 +1077,16 @@ msgstr "mot de passe"
|
|||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr "Changer le mot de passe"
|
msgstr "Changer le mot de passe"
|
||||||
|
|
||||||
#: apps/member/templates/member/includes/profile_info.html:55
|
#: apps/member/templates/member/includes/profile_info.html:28
|
||||||
|
#: apps/note/models/notes.py:244
|
||||||
|
msgid "friendships"
|
||||||
|
msgstr "amitiés"
|
||||||
|
|
||||||
|
#: apps/member/templates/member/includes/profile_info.html:32
|
||||||
|
msgid "Manage friendships"
|
||||||
|
msgstr "Gérer les amitiés"
|
||||||
|
|
||||||
|
#: apps/member/templates/member/includes/profile_info.html:63
|
||||||
msgid "API token"
|
msgid "API token"
|
||||||
msgstr "Accès API"
|
msgstr "Accès API"
|
||||||
|
|
||||||
@ -1148,6 +1158,23 @@ msgstr "Cliquez ici pour renvoyer un lien de validation."
|
|||||||
msgid "View my memberships"
|
msgid "View my memberships"
|
||||||
msgstr "Voir mes adhésions"
|
msgstr "Voir mes adhésions"
|
||||||
|
|
||||||
|
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||||
|
msgid "Note friendships"
|
||||||
|
msgstr "Amitiés note"
|
||||||
|
|
||||||
|
#: apps/member/templates/member/profile_trust.html:28
|
||||||
|
msgid ""
|
||||||
|
"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."
|
||||||
|
msgstr ""
|
||||||
|
"Ajouter quelqu'un⋅e en ami⋅e lui permet de me prélever de l'argent (tant que "
|
||||||
|
"ma note reste positive). Ceci sert à simplifier les remboursements entre "
|
||||||
|
"ami⋅es via note. En effet, une personne peut effectuer tous les transferts "
|
||||||
|
"sans posséder de droits supplémentaires."
|
||||||
|
|
||||||
#: apps/member/templates/member/profile_update.html:18
|
#: apps/member/templates/member/profile_update.html:18
|
||||||
msgid "Save Changes"
|
msgid "Save Changes"
|
||||||
msgstr "Sauvegarder les changements"
|
msgstr "Sauvegarder les changements"
|
||||||
@ -1156,47 +1183,47 @@ msgstr "Sauvegarder les changements"
|
|||||||
msgid "Registrations"
|
msgid "Registrations"
|
||||||
msgstr "Inscriptions"
|
msgstr "Inscriptions"
|
||||||
|
|
||||||
#: apps/member/views.py:72 apps/registration/forms.py:23
|
#: apps/member/views.py:73 apps/registration/forms.py:23
|
||||||
msgid "This address must be valid."
|
msgid "This address must be valid."
|
||||||
msgstr "Cette adresse doit être valide."
|
msgstr "Cette adresse doit être valide."
|
||||||
|
|
||||||
#: apps/member/views.py:139
|
#: apps/member/views.py:140
|
||||||
msgid "Profile detail"
|
msgid "Profile detail"
|
||||||
msgstr "Détails de l'utilisateur"
|
msgstr "Détails de l'utilisateur"
|
||||||
|
|
||||||
#: apps/member/views.py:205
|
#: apps/member/views.py:206
|
||||||
msgid "Search user"
|
msgid "Search user"
|
||||||
msgstr "Chercher un utilisateur"
|
msgstr "Chercher un utilisateur"
|
||||||
|
|
||||||
#: apps/member/views.py:273
|
#: apps/member/views.py:308
|
||||||
msgid "Update note picture"
|
msgid "Update note picture"
|
||||||
msgstr "Modifier la photo de la note"
|
msgstr "Modifier la photo de la note"
|
||||||
|
|
||||||
#: apps/member/views.py:319
|
#: apps/member/views.py:354
|
||||||
msgid "Manage auth token"
|
msgid "Manage auth token"
|
||||||
msgstr "Gérer les jetons d'authentification"
|
msgstr "Gérer les jetons d'authentification"
|
||||||
|
|
||||||
#: apps/member/views.py:346
|
#: apps/member/views.py:381
|
||||||
msgid "Create new club"
|
msgid "Create new club"
|
||||||
msgstr "Créer un nouveau club"
|
msgstr "Créer un nouveau club"
|
||||||
|
|
||||||
#: apps/member/views.py:365
|
#: apps/member/views.py:400
|
||||||
msgid "Search club"
|
msgid "Search club"
|
||||||
msgstr "Chercher un club"
|
msgstr "Chercher un club"
|
||||||
|
|
||||||
#: apps/member/views.py:398
|
#: apps/member/views.py:433
|
||||||
msgid "Club detail"
|
msgid "Club detail"
|
||||||
msgstr "Détails du club"
|
msgstr "Détails du club"
|
||||||
|
|
||||||
#: apps/member/views.py:479
|
#: apps/member/views.py:540
|
||||||
msgid "Update club"
|
msgid "Update club"
|
||||||
msgstr "Modifier le club"
|
msgstr "Modifier le club"
|
||||||
|
|
||||||
#: apps/member/views.py:513
|
#: apps/member/views.py:574
|
||||||
msgid "Add new member to the club"
|
msgid "Add new member to the club"
|
||||||
msgstr "Ajouter un nouveau membre au club"
|
msgstr "Ajouter un nouveau membre au club"
|
||||||
|
|
||||||
#: apps/member/views.py:642 apps/wei/views.py:973
|
#: apps/member/views.py:703 apps/wei/views.py:973
|
||||||
msgid ""
|
msgid ""
|
||||||
"This user don't have enough money to join this club, and can't have a "
|
"This user don't have enough money to join this club, and can't have a "
|
||||||
"negative balance."
|
"negative balance."
|
||||||
@ -1204,19 +1231,19 @@ msgstr ""
|
|||||||
"Cet utilisateur n'a pas assez d'argent pour rejoindre ce club et ne peut pas "
|
"Cet utilisateur n'a pas assez d'argent pour rejoindre ce club et ne peut pas "
|
||||||
"avoir un solde négatif."
|
"avoir un solde négatif."
|
||||||
|
|
||||||
#: apps/member/views.py:664
|
#: apps/member/views.py:725
|
||||||
msgid "The membership must start after {:%m-%d-%Y}."
|
msgid "The membership must start after {:%m-%d-%Y}."
|
||||||
msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
|
msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
|
||||||
|
|
||||||
#: apps/member/views.py:669
|
#: apps/member/views.py:730
|
||||||
msgid "The membership must begin before {:%m-%d-%Y}."
|
msgid "The membership must begin before {:%m-%d-%Y}."
|
||||||
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
|
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
|
||||||
|
|
||||||
#: apps/member/views.py:815
|
#: apps/member/views.py:876
|
||||||
msgid "Manage roles of an user in the club"
|
msgid "Manage roles of an user in the club"
|
||||||
msgstr "Gérer les rôles d'un utilisateur dans le club"
|
msgstr "Gérer les rôles d'un utilisateur dans le club"
|
||||||
|
|
||||||
#: apps/member/views.py:840
|
#: apps/member/views.py:901
|
||||||
msgid "Members of the club"
|
msgid "Members of the club"
|
||||||
msgstr "Membres du club"
|
msgstr "Membres du club"
|
||||||
|
|
||||||
@ -1234,7 +1261,7 @@ msgstr "destination"
|
|||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr "montant"
|
msgstr "montant"
|
||||||
|
|
||||||
#: apps/note/api/serializers.py:183 apps/note/api/serializers.py:189
|
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||||
#: apps/note/models/transactions.py:228
|
#: apps/note/models/transactions.py:228
|
||||||
msgid ""
|
msgid ""
|
||||||
"The transaction can't be saved since the source note or the destination note "
|
"The transaction can't be saved since the source note or the destination note "
|
||||||
@ -1366,30 +1393,47 @@ msgstr "note spéciale"
|
|||||||
msgid "special notes"
|
msgid "special notes"
|
||||||
msgstr "notes spéciales"
|
msgstr "notes spéciales"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:237
|
#: apps/note/models/notes.py:232
|
||||||
|
msgid "trusting"
|
||||||
|
msgstr "note"
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:239
|
||||||
|
msgid "trusted"
|
||||||
|
msgstr "ami"
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:243
|
||||||
|
msgid "frienship"
|
||||||
|
msgstr "amitié"
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:248
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Friendship between {trusting} and {trusted}"
|
||||||
|
msgstr "Amitié entre {trusting} et {trusted}"
|
||||||
|
|
||||||
|
#: apps/note/models/notes.py:269
|
||||||
msgid "Invalid alias"
|
msgid "Invalid alias"
|
||||||
msgstr "Alias invalide"
|
msgstr "Alias invalide"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:254
|
#: apps/note/models/notes.py:286
|
||||||
msgid "alias"
|
msgid "alias"
|
||||||
msgstr "alias"
|
msgstr "alias"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:278
|
#: apps/note/models/notes.py:310
|
||||||
msgid "Alias is too long."
|
msgid "Alias is too long."
|
||||||
msgstr "L'alias est trop long."
|
msgstr "L'alias est trop long."
|
||||||
|
|
||||||
#: apps/note/models/notes.py:281
|
#: apps/note/models/notes.py:313
|
||||||
msgid ""
|
msgid ""
|
||||||
"This alias contains only complex character. Please use a more simple alias."
|
"This alias contains only complex character. Please use a more simple alias."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Cet alias ne contient que des caractères complexes. Merci d'utiliser un "
|
"Cet alias ne contient que des caractères complexes. Merci d'utiliser un "
|
||||||
"alias plus simple."
|
"alias plus simple."
|
||||||
|
|
||||||
#: apps/note/models/notes.py:285
|
#: apps/note/models/notes.py:317
|
||||||
msgid "An alias with a similar name already exists: {} "
|
msgid "An alias with a similar name already exists: {} "
|
||||||
msgstr "Un alias avec un nom similaire existe déjà : {} "
|
msgstr "Un alias avec un nom similaire existe déjà : {} "
|
||||||
|
|
||||||
#: apps/note/models/notes.py:299
|
#: apps/note/models/notes.py:331
|
||||||
msgid "You can't delete your main alias."
|
msgid "You can't delete your main alias."
|
||||||
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
||||||
|
|
||||||
@ -1535,7 +1579,8 @@ msgstr "Cliquez pour valider"
|
|||||||
msgid "No reason specified"
|
msgid "No reason specified"
|
||||||
msgstr "Pas de motif spécifié"
|
msgstr "Pas de motif spécifié"
|
||||||
|
|
||||||
#: apps/note/tables.py:169 apps/note/tables.py:203 apps/treasury/tables.py:39
|
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||||
|
#: apps/treasury/tables.py:39
|
||||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
||||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
||||||
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
||||||
@ -1546,7 +1591,7 @@ msgstr "Pas de motif spécifié"
|
|||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
#: apps/note/tables.py:197 apps/note/templates/note/conso_form.html:132
|
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||||
#: apps/wei/templates/wei/base.html:89
|
#: apps/wei/templates/wei/base.html:89
|
||||||
#: apps/wei/templates/wei/bus_detail.html:20
|
#: apps/wei/templates/wei/bus_detail.html:20
|
||||||
@ -1556,6 +1601,10 @@ msgstr "Supprimer"
|
|||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Éditer"
|
msgstr "Éditer"
|
||||||
|
|
||||||
|
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||||
|
msgid "Hide/Show"
|
||||||
|
msgstr "Afficher/Masquer"
|
||||||
|
|
||||||
#: apps/note/templates/note/conso_form.html:22
|
#: apps/note/templates/note/conso_form.html:22
|
||||||
#: apps/note/templates/note/transaction_form.html:48
|
#: apps/note/templates/note/transaction_form.html:48
|
||||||
msgid "Please select a note"
|
msgid "Please select a note"
|
||||||
@ -1685,6 +1734,18 @@ msgstr "le bouton a bien été supprimé "
|
|||||||
msgid "Unable to delete button "
|
msgid "Unable to delete button "
|
||||||
msgstr "Impossible de supprimer le bouton "
|
msgstr "Impossible de supprimer le bouton "
|
||||||
|
|
||||||
|
#: apps/note/templates/note/transactiontemplate_list.html:95
|
||||||
|
msgid "Button hidden"
|
||||||
|
msgstr "Bouton masqué"
|
||||||
|
|
||||||
|
#: apps/note/templates/note/transactiontemplate_list.html:96
|
||||||
|
msgid "Button displayed"
|
||||||
|
msgstr "Bouton affiché"
|
||||||
|
|
||||||
|
#: apps/note/templates/note/transactiontemplate_list.html:100
|
||||||
|
msgid "An error occured"
|
||||||
|
msgstr "Une erreur s'est produite"
|
||||||
|
|
||||||
#: apps/note/views.py:36
|
#: apps/note/views.py:36
|
||||||
msgid "Transfer money"
|
msgid "Transfer money"
|
||||||
msgstr "Transférer de l'argent"
|
msgstr "Transférer de l'argent"
|
||||||
@ -1701,7 +1762,7 @@ msgstr "Chercher un bouton"
|
|||||||
msgid "Update button"
|
msgid "Update button"
|
||||||
msgstr "Modifier le bouton"
|
msgstr "Modifier le bouton"
|
||||||
|
|
||||||
#: apps/note/views.py:151 note_kfet/templates/base.html:67
|
#: apps/note/views.py:151 note_kfet/templates/base.html:66
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr "Consommations"
|
msgstr "Consommations"
|
||||||
|
|
||||||
@ -1899,7 +1960,7 @@ msgstr ""
|
|||||||
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
|
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
|
||||||
"avec ces paramètres. Merci de les corriger et de réessayer."
|
"avec ces paramètres. Merci de les corriger et de réessayer."
|
||||||
|
|
||||||
#: apps/permission/views.py:112 note_kfet/templates/base.html:109
|
#: apps/permission/views.py:112 note_kfet/templates/base.html:108
|
||||||
msgid "Rights"
|
msgid "Rights"
|
||||||
msgstr "Droits"
|
msgstr "Droits"
|
||||||
|
|
||||||
@ -2106,7 +2167,7 @@ msgstr ""
|
|||||||
msgid "Invalidate pre-registration"
|
msgid "Invalidate pre-registration"
|
||||||
msgstr "Invalider l'inscription"
|
msgstr "Invalider l'inscription"
|
||||||
|
|
||||||
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:97
|
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:96
|
||||||
msgid "Treasury"
|
msgid "Treasury"
|
||||||
msgstr "Trésorerie"
|
msgstr "Trésorerie"
|
||||||
|
|
||||||
@ -2514,7 +2575,7 @@ msgstr "Gérer les crédits de la Société générale"
|
|||||||
|
|
||||||
#: apps/wei/apps.py:10 apps/wei/models.py:50 apps/wei/models.py:51
|
#: apps/wei/apps.py:10 apps/wei/models.py:50 apps/wei/models.py:51
|
||||||
#: apps/wei/models.py:62 apps/wei/models.py:180
|
#: apps/wei/models.py:62 apps/wei/models.py:180
|
||||||
#: note_kfet/templates/base.html:103
|
#: note_kfet/templates/base.html:102
|
||||||
msgid "WEI"
|
msgid "WEI"
|
||||||
msgstr "WEI"
|
msgstr "WEI"
|
||||||
|
|
||||||
@ -2522,7 +2583,7 @@ msgstr "WEI"
|
|||||||
msgid "The selected user is not validated. Please validate its account first"
|
msgid "The selected user is not validated. Please validate its account first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"L'utilisateur sélectionné n'est pas validé. Merci de d'abord valider son "
|
"L'utilisateur sélectionné n'est pas validé. Merci de d'abord valider son "
|
||||||
"compte."
|
"compte"
|
||||||
|
|
||||||
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
|
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
|
||||||
#: apps/wei/models.py:323
|
#: apps/wei/models.py:323
|
||||||
@ -2563,7 +2624,7 @@ msgstr "Sélectionnez les rôles qui vous intéressent."
|
|||||||
msgid "This team doesn't belong to the given bus."
|
msgid "This team doesn't belong to the given bus."
|
||||||
msgstr "Cette équipe n'appartient pas à ce bus."
|
msgstr "Cette équipe n'appartient pas à ce bus."
|
||||||
|
|
||||||
#: apps/wei/forms/surveys/wei2021.py:35
|
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:35
|
||||||
msgid "Choose a word:"
|
msgid "Choose a word:"
|
||||||
msgstr "Choisissez un mot :"
|
msgstr "Choisissez un mot :"
|
||||||
|
|
||||||
@ -3124,19 +3185,19 @@ msgstr "Répartir les 1A dans les bus"
|
|||||||
msgid "Attribute bus"
|
msgid "Attribute bus"
|
||||||
msgstr "Attribuer un bus"
|
msgstr "Attribuer un bus"
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:161
|
#: note_kfet/settings/base.py:172
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr "Allemand"
|
msgstr "Allemand"
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:162
|
#: note_kfet/settings/base.py:173
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr "Anglais"
|
msgstr "Anglais"
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:163
|
#: note_kfet/settings/base.py:174
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr "Espagnol"
|
msgstr "Espagnol"
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:164
|
#: note_kfet/settings/base.py:175
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr "Français"
|
msgstr "Français"
|
||||||
|
|
||||||
@ -3193,7 +3254,7 @@ msgstr ""
|
|||||||
"erreur, qui sera corrigée rapidement. Vous pouvez désormais aller boire une "
|
"erreur, qui sera corrigée rapidement. Vous pouvez désormais aller boire une "
|
||||||
"bière."
|
"bière."
|
||||||
|
|
||||||
#: note_kfet/templates/autocomplete_model.html:14
|
#: note_kfet/templates/autocomplete_model.html:15
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr "Réinitialiser"
|
msgstr "Réinitialiser"
|
||||||
|
|
||||||
@ -3201,34 +3262,34 @@ msgstr "Réinitialiser"
|
|||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:79
|
#: note_kfet/templates/base.html:78
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilisateurs"
|
msgstr "Utilisateurs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:85
|
#: note_kfet/templates/base.html:84
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr "Clubs"
|
msgstr "Clubs"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:114
|
#: note_kfet/templates/base.html:113
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr "Admin"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:128
|
#: note_kfet/templates/base.html:127
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "Mon compte"
|
msgstr "Mon compte"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:131
|
#: note_kfet/templates/base.html:130
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Se déconnecter"
|
msgstr "Se déconnecter"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:139
|
#: note_kfet/templates/base.html:138
|
||||||
#: note_kfet/templates/registration/signup.html:6
|
#: note_kfet/templates/registration/signup.html:6
|
||||||
#: note_kfet/templates/registration/signup.html:11
|
#: note_kfet/templates/registration/signup.html:11
|
||||||
#: note_kfet/templates/registration/signup.html:28
|
#: note_kfet/templates/registration/signup.html:28
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Inscription"
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:146
|
#: note_kfet/templates/base.html:145
|
||||||
#: note_kfet/templates/registration/login.html:6
|
#: note_kfet/templates/registration/login.html:6
|
||||||
#: note_kfet/templates/registration/login.html:15
|
#: note_kfet/templates/registration/login.html:15
|
||||||
#: note_kfet/templates/registration/login.html:38
|
#: note_kfet/templates/registration/login.html:38
|
||||||
@ -3236,7 +3297,7 @@ msgstr "Inscription"
|
|||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr "Se connecter"
|
msgstr "Se connecter"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:160
|
#: note_kfet/templates/base.html:159
|
||||||
msgid ""
|
msgid ""
|
||||||
"You are not a BDE member anymore. Please renew your membership if you want "
|
"You are not a BDE member anymore. Please renew your membership if you want "
|
||||||
"to use the note."
|
"to use the note."
|
||||||
@ -3244,7 +3305,7 @@ msgstr ""
|
|||||||
"Vous n'êtes plus adhérent BDE. Merci de réadhérer si vous voulez profiter de "
|
"Vous n'êtes plus adhérent BDE. Merci de réadhérer si vous voulez profiter de "
|
||||||
"la note."
|
"la note."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:166
|
#: note_kfet/templates/base.html:165
|
||||||
msgid ""
|
msgid ""
|
||||||
"Your e-mail address is not validated. Please check your mail inbox and click "
|
"Your e-mail address is not validated. Please check your mail inbox and click "
|
||||||
"on the validation link."
|
"on the validation link."
|
||||||
@ -3252,7 +3313,7 @@ msgstr ""
|
|||||||
"Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
|
"Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
|
||||||
"et de cliquer sur le lien de validation."
|
"et de cliquer sur le lien de validation."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:172
|
#: note_kfet/templates/base.html:171
|
||||||
msgid ""
|
msgid ""
|
||||||
"You declared that you opened a bank account in the Société générale. The "
|
"You declared that you opened a bank account in the Société générale. The "
|
||||||
"bank did not validate the creation of the account to the BDE, so the "
|
"bank did not validate the creation of the account to the BDE, so the "
|
||||||
@ -3266,11 +3327,11 @@ msgstr ""
|
|||||||
"vérification peut durer quelques jours. Merci de vous assurer de bien aller "
|
"vérification peut durer quelques jours. Merci de vous assurer de bien aller "
|
||||||
"au bout de vos démarches."
|
"au bout de vos démarches."
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:195
|
#: note_kfet/templates/base.html:194
|
||||||
msgid "Contact us"
|
msgid "Contact us"
|
||||||
msgstr "Nous contacter"
|
msgstr "Nous contacter"
|
||||||
|
|
||||||
#: note_kfet/templates/base.html:197
|
#: note_kfet/templates/base.html:196
|
||||||
msgid "Technical Support"
|
msgid "Technical Support"
|
||||||
msgstr "Support technique"
|
msgstr "Support technique"
|
||||||
|
|
||||||
@ -3501,8 +3562,3 @@ msgstr ""
|
|||||||
"vous connecter. Vous devez vous rendre à la Kfet et payer les frais "
|
"vous connecter. Vous devez vous rendre à la Kfet et payer les frais "
|
||||||
"d'adhésion. Vous devez également valider votre adresse email en suivant le "
|
"d'adhésion. Vous devez également valider votre adresse email en suivant le "
|
||||||
"lien que vous avez reçu."
|
"lien que vous avez reçu."
|
||||||
|
|
||||||
#~ msgid "You are not a Kfet member, so you can't use your note account."
|
|
||||||
#~ msgstr ""
|
|
||||||
#~ "Vous n'êtes pas adhérent Kfet, vous ne pouvez par conséquent pas utiliser "
|
|
||||||
#~ "votre compte note."
|
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
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
|
||||||
|
|
||||||
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__))))
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
@ -22,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
|
||||||
|
|
||||||
@ -241,13 +252,15 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
||||||
],
|
],
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
'DEFAULT_PAGINATION_CLASS': 'apps.api.pagination.CustomPagination',
|
||||||
'PAGE_SIZE': 20,
|
'PAGE_SIZE': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
# OAuth2 Provider
|
# OAuth2 Provider
|
||||||
OAUTH2_PROVIDER = {
|
OAUTH2_PROVIDER = {
|
||||||
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
|
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
|
||||||
|
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
|
||||||
|
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Take control on how widget templates are sourced
|
# Take control on how widget templates are sourced
|
||||||
|
72
note_kfet/static/css/custom.css
Normal file → Executable file
72
note_kfet/static/css/custom.css
Normal file → Executable file
@ -65,7 +65,10 @@ mark {
|
|||||||
|
|
||||||
/* Last BDE colors */
|
/* Last BDE colors */
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
background-color: rgb(18, 67, 4) !important;
|
/* background-color: rgb(18, 67, 4) !important; */
|
||||||
|
/* MODE VIEUXCON=ON */
|
||||||
|
/* background-color: rgb(166, 0, 2) !important; */
|
||||||
|
background-color: rgb(0, 0, 0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@ -80,15 +83,15 @@ body {
|
|||||||
.btn-outline-primary:hover,
|
.btn-outline-primary:hover,
|
||||||
.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: rgb(241, 229, 52);
|
||||||
background-color: rgb(18, 67, 46);
|
background-color: rgb(228, 35, 132);
|
||||||
border-color: rgb(18, 67, 46);
|
border-color: rgb(228, 35, 132);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-primary {
|
.btn-outline-primary {
|
||||||
color: rgb(18, 67, 46);
|
color: #fff;
|
||||||
background-color: rgba(248, 249, 250, 0.9);
|
background-color: #000;
|
||||||
border-color: rgb(18, 67, 46);
|
border-color: #464647;
|
||||||
}
|
}
|
||||||
|
|
||||||
.turbolinks-progress-bar {
|
.turbolinks-progress-bar {
|
||||||
@ -98,36 +101,63 @@ body {
|
|||||||
.btn-primary:hover,
|
.btn-primary:hover,
|
||||||
.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: rgb(241, 229, 52);
|
||||||
background-color: rgb(18, 67, 46);
|
background-color: rgb(228, 35, 132);
|
||||||
border-color: rgb(18, 67, 46);
|
border-color: rgb(228, 35, 132);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
color: rgba(248, 249, 250, 0.9);
|
color: #fff;
|
||||||
background-color: rgb(28, 114, 10);
|
background-color: #000;
|
||||||
border-color: rgb(18, 67, 46);
|
border-color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-primary {
|
.border-primary {
|
||||||
border-color: rgb(28, 114, 10) !important;
|
border-color: rgb(228, 35, 132) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #000;
|
||||||
|
border-color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover,
|
||||||
|
.btn-secondary:not(:disabled):not(.disabled).active,
|
||||||
|
.btn-secondary:not(:disabled):not(.disabled):active {
|
||||||
|
color: rgb(241, 229, 52);
|
||||||
|
background-color: rgb(228, 35, 132);
|
||||||
|
border-color: rgb(228, 35, 132);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-outline-dark {
|
||||||
|
color: #343a40;
|
||||||
|
border-color: #343a40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-dark:hover,
|
||||||
|
.btn-outline-dark:not(:disabled):not(.disabled).active,
|
||||||
|
.btn-outline-dark:not(:disabled):not(.disabled):active {
|
||||||
|
color: rgb(241, 229, 52);
|
||||||
|
background-color: rgb(228, 35, 132);
|
||||||
|
border-color: rgb(228, 35, 132);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgb(28, 114, 10);
|
color: rgb(228, 35, 132);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: rgb(122, 163, 75);
|
color: rgb(228, 35, 132);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 rgb(228 35 132 / 50%);
|
||||||
border-color: rgb(122, 163, 75);
|
border-color: rgb(228, 35, 132);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 rgb(228 35 132 / 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
6
note_kfet/static/js/turbolinks.js
Normal file
6
note_kfet/static/js/turbolinks.js
Normal file
File diff suppressed because one or more lines are too long
@ -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>
|
|
||||||
|
@ -33,8 +33,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<script src="{% static "jquery/jquery.min.js" %}"></script>
|
<script src="{% static "jquery/jquery.min.js" %}"></script>
|
||||||
<script src="{% static "popper.js/umd/popper.min.js" %}"></script>
|
<script src="{% static "popper.js/umd/popper.min.js" %}"></script>
|
||||||
<script src="{% static "bootstrap4/js/bootstrap.min.js" %}"></script>
|
<script src="{% static "bootstrap4/js/bootstrap.min.js" %}"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
|
<script src="{% static "js/turbolinks.js" %}"></script>
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="{% static "js/base.js" %}"></script>
|
<script src="{% static "js/base.js" %}"></script>
|
||||||
<script src="{% static "js/konami.js" %}"></script>
|
<script src="{% static "js/konami.js" %}"></script>
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
{{ profile_form|crispy }}
|
{{ profile_form|crispy }}
|
||||||
{{ soge_form|crispy }}
|
{% comment "Soge not for membership (only WEI)" %} {{ soge_form|crispy }} {% endcomment %}
|
||||||
<button class="btn btn-success" type="submit">
|
<button class="btn btn-success" type="submit">
|
||||||
{% trans "Sign up" %}
|
{% trans "Sign up" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -17,3 +17,6 @@ django-tables2~=2.3.1
|
|||||||
python-memcached~=1.59
|
python-memcached~=1.59
|
||||||
phonenumbers~=8.9.10
|
phonenumbers~=8.9.10
|
||||||
Pillow>=5.4.1
|
Pillow>=5.4.1
|
||||||
|
oauthlib<3.2.1
|
||||||
|
lxml<4.9.2
|
||||||
|
zipp>=2.0.0,<2.0.1
|
||||||
|
2
tox.ini
2
tox.ini
@ -16,7 +16,7 @@ skipsdist = True
|
|||||||
sitepackages = True
|
sitepackages = True
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
coverage
|
coverage<6.5.0
|
||||||
commands =
|
commands =
|
||||||
coverage run --omit='apps/scripts*,*_example.py,note_kfet/wsgi.py' --source=apps,note_kfet ./manage.py test apps/
|
coverage run --omit='apps/scripts*,*_example.py,note_kfet/wsgi.py' --source=apps,note_kfet ./manage.py test apps/
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
Reference in New Issue
Block a user