mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 09:58:23 +02:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
e6f3084588 | |||
145e55da75 | |||
d3ba95cdca | |||
8ffb0ebb56 | |||
5038af9e34 | |||
819b4214c9 | |||
b8a93b0b75 | |||
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 |
@ -12,7 +12,7 @@ RUN apt-get update && \
|
|||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools \
|
python3-bs4 python3-setuptools \
|
||||||
uwsgi uwsgi-plugin-python3 \
|
uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 && \
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Instal PyPI requirements
|
# Instal PyPI requirements
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
|
|||||||
$ sudo apt update
|
$ sudo apt update
|
||||||
$ sudo apt install --no-install-recommends -y \
|
$ sudo apt install --no-install-recommends -y \
|
||||||
ipython3 python3-setuptools python3-venv python3-dev \
|
ipython3 python3-setuptools python3-venv python3-dev \
|
||||||
texlive-xetex gettext libjs-bootstrap4 git
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Clonage du dépot** là où vous voulez :
|
2. **Clonage du dépot** là où vous voulez :
|
||||||
@ -115,7 +115,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
|||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools python3-docutils \
|
python3-bs4 python3-setuptools python3-docutils \
|
||||||
memcached uwsgi uwsgi-plugin-python3 \
|
memcached uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 \
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
||||||
nginx python3-venv git acl
|
nginx python3-venv git acl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -17,6 +18,7 @@
|
|||||||
- ipython3
|
- ipython3
|
||||||
|
|
||||||
# Front-end dependencies
|
# Front-end dependencies
|
||||||
|
- fonts-font-awesome
|
||||||
- libjs-bootstrap4
|
- libjs-bootstrap4
|
||||||
|
|
||||||
# Python dependencies
|
# Python dependencies
|
||||||
|
@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
|
<input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
|
||||||
|
<button id="trigger" class="btn btn-secondary">Click me !</button>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
@ -63,15 +64,46 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
refreshBalance();
|
refreshBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function process_qrcode() {
|
||||||
|
let name = alias_obj.val();
|
||||||
|
$.get("/api/note/note?search=" + name + "&format=json").done(
|
||||||
|
function (res) {
|
||||||
|
let note = res.results[0];
|
||||||
|
$.post("/api/activity/entry/?format=json", {
|
||||||
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
|
activity: {{ activity.id }},
|
||||||
|
note: note.id,
|
||||||
|
guest: null
|
||||||
|
}).done(function () {
|
||||||
|
addMsg(interpolate(gettext(
|
||||||
|
"Entry made for %s whose balance is %s €"),
|
||||||
|
[note.name, note.balance / 100]), "success", 4000);
|
||||||
|
reloadTable(true);
|
||||||
|
}).fail(function (xhr) {
|
||||||
|
errMsg(xhr.responseJSON, 4000);
|
||||||
|
});
|
||||||
|
}).fail(function (xhr) {
|
||||||
|
errMsg(xhr.responseJSON, 4000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
alias_obj.keyup(function(event) {
|
alias_obj.keyup(function(event) {
|
||||||
let code = event.originalEvent.keyCode
|
let code = event.originalEvent.keyCode
|
||||||
if (65 <= code <= 122 || code === 13) {
|
if (65 <= code <= 122 || code === 13) {
|
||||||
debounce(reloadTable)()
|
debounce(reloadTable)()
|
||||||
}
|
}
|
||||||
|
if (code === 0)
|
||||||
|
process_qrcode();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).ready(init);
|
$(document).ready(init);
|
||||||
|
|
||||||
|
alias_obj2 = document.getElementById("alias");
|
||||||
|
$("#trigger").click(function (e) {
|
||||||
|
addMsg("Clicked", "success", 1000);
|
||||||
|
alias_obj.val(alias_obj.val() + "\0");
|
||||||
|
alias_obj2.dispatchEvent(new KeyboardEvent('keyup'));
|
||||||
|
})
|
||||||
function init() {
|
function init() {
|
||||||
$(".table-row").click(function (e) {
|
$(".table-row").click(function (e) {
|
||||||
let target = e.target.parentElement;
|
let target = e.target.parentElement;
|
||||||
@ -168,4 +200,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -34,9 +34,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
||||||
<svg class="bi bi-calendar-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-calendar-plus-o" aria-hidden="true"></i>
|
||||||
<path d="M4 .5a.5.5 0 0 0-1 0V1H2a2 2 0 0 0-2 2v1h16V3a2 2 0 0 0-2-2h-1V.5a.5.5 0 0 0-1 0V1H4V.5zM16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2zM8.5 8.5V10H10a.5.5 0 0 1 0 1H8.5v1.5a.5.5 0 0 1-1 0V11H6a.5.5 0 0 1 0-1h1.5V8.5a.5.5 0 0 1 1 0z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'New activity' %}
|
{% trans 'New activity' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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)
|
||||||
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -45,10 +45,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
{% if user_object %}
|
{% if user_object %}
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
||||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Update Profile' %}
|
|
||||||
</a>
|
</a>
|
||||||
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
||||||
{% if request.path_info != user_profile_url %}
|
{% if request.path_info != user_profile_url %}
|
||||||
@ -62,10 +59,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if ".change_"|has_perm:club %}
|
{% if ".change_"|has_perm:club %}
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
||||||
data-turbolinks="false">
|
data-turbolinks="false">
|
||||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Update Profile' %}
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'member:club_detail' club.pk as club_detail_url %}
|
{% url 'member:club_detail' club.pk as club_detail_url %}
|
||||||
|
@ -10,10 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-users"></i> {% trans "Club managers" %}
|
||||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Club managers" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table managers %}
|
{% render_table managers %}
|
||||||
@ -26,12 +23,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
||||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-users"></i> {% trans "Club members" %}
|
||||||
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
|
|
||||||
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Club members" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table member_list %}
|
{% render_table member_list %}
|
||||||
@ -45,10 +37,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
||||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Transaction history" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
|
@ -47,9 +47,7 @@
|
|||||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-edit"></i>
|
||||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -11,9 +11,7 @@
|
|||||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
||||||
<svg class="bi bi-lock" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-lock"></i>
|
||||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Change password' %}
|
{% trans 'Change password' %}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@ -22,9 +20,7 @@
|
|||||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-edit"></i>
|
||||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@ -56,10 +52,10 @@
|
|||||||
{% if user_object.pk == user.pk %}
|
{% if user_object.pk == user.pk %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||||
<svg class="bi bi-cogs" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-cogs"></i> {% trans 'API token' %}
|
||||||
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
</a>
|
||||||
</svg>
|
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
|
||||||
{% trans 'API token' %}
|
<i class="fa fa-qrcode"></i> {% trans 'QR Code' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -18,10 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-users"></i> {% trans "View my memberships" %}
|
||||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "View my memberships" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table club_list %}
|
{% render_table club_list %}
|
||||||
@ -32,10 +29,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||||
{% if "note.view_note"|has_perm:user_object.note %}
|
{% if "note.view_note"|has_perm:user_object.note %}
|
||||||
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
||||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Transaction history" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
|
36
apps/member/templates/member/qr_code.html
Normal file
36
apps/member/templates/member/qr_code.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card bg-light">
|
||||||
|
<h3 class="card-header text-center">
|
||||||
|
{% trans "QR Code for" %} {{ user_object.username }} ({{ user_object.first_name }} {{user_object.last_name }})
|
||||||
|
</h3>
|
||||||
|
<div class="text-center" id="qrcode">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script>
|
||||||
|
var qrc = new QRCode(document.getElementById("qrcode"), {
|
||||||
|
text: "{{ user_object.pk }}\0",
|
||||||
|
width: 1024,
|
||||||
|
height: 1024
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extracss %}
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
@ -7,11 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% if can_manage_registrations %}
|
{% if can_manage_registrations %}
|
||||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
||||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Registrations" %}
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -24,4 +24,5 @@ urlpatterns = [
|
|||||||
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('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
||||||
|
path('user/<int:pk>/qr_code/', views.QRCodeView.as_view(), name='qr_code'),
|
||||||
]
|
]
|
||||||
|
@ -18,7 +18,7 @@ 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, NoteUser, NoteClub
|
||||||
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
|
||||||
from note_kfet.middlewares import _set_current_request
|
from note_kfet.middlewares import _set_current_request
|
||||||
@ -174,7 +174,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 +183,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
|
||||||
|
|
||||||
@ -256,7 +256,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="",
|
||||||
@ -330,6 +331,14 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
|||||||
context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
|
context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class QRCodeView(LoginRequiredMixin, DetailView):
|
||||||
|
"""
|
||||||
|
Affiche le QR Code
|
||||||
|
"""
|
||||||
|
model = User
|
||||||
|
context_object_name = "user_object"
|
||||||
|
template_name = "member/qr_code.html"
|
||||||
|
extra_context = {"title": _("QR Code")}
|
||||||
|
|
||||||
# ******************************* #
|
# ******************************* #
|
||||||
# CLUB #
|
# CLUB #
|
||||||
@ -403,9 +412,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 +455,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
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
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
|
||||||
@ -197,6 +197,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 +215,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)
|
||||||
|
@ -129,10 +129,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{# Mode switch #}
|
{# Mode switch #}
|
||||||
<div class="card-footer border-primary">
|
<div class="card-footer border-primary">
|
||||||
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
||||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Edit" %}
|
|
||||||
</a>
|
</a>
|
||||||
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
||||||
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
||||||
|
@ -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"
|
||||||
@ -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",
|
||||||
@ -2871,6 +2871,102 @@
|
|||||||
"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.role",
|
"model": "permission.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@ -2901,7 +2997,11 @@
|
|||||||
126,
|
126,
|
||||||
161,
|
161,
|
||||||
162,
|
162,
|
||||||
165
|
165,
|
||||||
|
186,
|
||||||
|
187,
|
||||||
|
188,
|
||||||
|
189
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3010,7 +3110,9 @@
|
|||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
168,
|
168,
|
||||||
182
|
182,
|
||||||
|
184,
|
||||||
|
185
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3278,7 +3380,13 @@
|
|||||||
180,
|
180,
|
||||||
181,
|
181,
|
||||||
182,
|
182,
|
||||||
183
|
183,
|
||||||
|
184,
|
||||||
|
185,
|
||||||
|
186,
|
||||||
|
187,
|
||||||
|
188,
|
||||||
|
189
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3338,7 +3446,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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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
|
||||||
|
293
apps/wei/forms/surveys/wei2022.py
Normal file
293
apps/wei/forms/surveys/wei2022.py
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
# 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 = [
|
||||||
|
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
|
||||||
|
'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill',
|
||||||
|
'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial',
|
||||||
|
'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
|
||||||
|
'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
|
||||||
|
'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
|
||||||
|
'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
|
||||||
|
'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
@ -29,10 +29,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Teams" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table teams %}
|
{% render_table teams %}
|
||||||
@ -45,10 +42,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-bus"></i> {% trans "Members" %}
|
||||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Members" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table memberships %}
|
{% render_table memberships %}
|
||||||
@ -57,13 +51,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<a href="{% url 'wei:wei_memberships_bus_pdf' wei_pk=club.pk bus_pk=object.pk %}" data-turbolinks="false">
|
<a href="{% url 'wei:wei_memberships_bus_pdf' wei_pk=club.pk bus_pk=object.pk %}" data-turbolinks="false">
|
||||||
<button class="btn btn-block btn-danger">
|
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
|
||||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "View as PDF" %}
|
|
||||||
</button>
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -47,10 +47,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Teams" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table memberships %}
|
{% render_table memberships %}
|
||||||
@ -60,13 +57,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
|
|
||||||
<a href="{% url 'wei:wei_memberships_team_pdf' wei_pk=club.pk bus_pk=object.bus.pk team_pk=object.pk %}"
|
<a href="{% url 'wei:wei_memberships_team_pdf' wei_pk=club.pk bus_pk=object.bus.pk team_pk=object.pk %}"
|
||||||
data-turbolinks="false">
|
data-turbolinks="false">
|
||||||
<button class="btn btn-block btn-danger">
|
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
|
||||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "View as PDF" %}
|
|
||||||
</button>
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -48,10 +48,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card bg-white mb-3">
|
<div class="card bg-white mb-3">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<span class="font-weight-bold">
|
<span class="font-weight-bold">
|
||||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-bus"></i> {% trans "Buses" %}
|
||||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Buses" %}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% render_table buses %}
|
{% render_table buses %}
|
||||||
@ -63,12 +60,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||||
href="{% url "wei:wei_memberships" pk=club.pk %}">
|
href="{% url "wei:wei_memberships" pk=club.pk %}">
|
||||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-users"></i> {% trans "Members of the WEI" %}
|
||||||
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
|
|
||||||
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Members of the WEI" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table member_list %}
|
{% render_table member_list %}
|
||||||
@ -80,10 +72,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
|
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
|
||||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Transaction history" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
@ -97,11 +86,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||||
href="{% url 'wei:wei_registrations' pk=club.pk %}">
|
href="{% url 'wei:wei_registrations' pk=club.pk %}">
|
||||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-user-plus"></i> {% trans "Unvalidated registrations" %}
|
||||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Unvalidated registrations" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
|
@ -28,13 +28,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</a>
|
</a>
|
||||||
<hr>
|
<hr>
|
||||||
<a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false">
|
<a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false">
|
||||||
<button class="btn btn-block btn-danger">
|
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
|
||||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "View as PDF" %}
|
|
||||||
</button>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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.
|
||||||
|
@ -23,7 +23,7 @@ Sur un Ubuntu/Debian :
|
|||||||
$ sudo apt update
|
$ sudo apt update
|
||||||
$ sudo apt install --no-install-recommends -y \
|
$ sudo apt install --no-install-recommends -y \
|
||||||
python3-setuptools python3-venv python3-dev \
|
python3-setuptools python3-venv python3-dev \
|
||||||
texlive-xetex gettext libjs-bootstrap4 git
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||||
|
|
||||||
Pour Arch Linux :
|
Pour Arch Linux :
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-back
|
|||||||
$ sudo apt update
|
$ sudo apt update
|
||||||
$ sudo apt install -t buster-backports --no-install-recommends \
|
$ sudo apt install -t buster-backports --no-install-recommends \
|
||||||
gettext git ipython3 \ # Dépendances basiques
|
gettext git ipython3 \ # Dépendances basiques
|
||||||
libjs-bootstrap4 \ # Pour l'affichage web
|
fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web
|
||||||
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
|
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
|
||||||
python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
|
python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
|
||||||
python3-djangorestframework python3-memcache python3-phonenumbers \
|
python3-djangorestframework python3-memcache python3-phonenumbers \
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ 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: 2021-10-07 22:55+0200\n"
|
||||||
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
"Last-Translator: Yohann D'ANELLO <ynerant@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"
|
||||||
@ -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"
|
||||||
|
|
||||||
@ -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 !"
|
||||||
|
|
||||||
@ -404,33 +404,33 @@ msgstr "Créer une nouvelle activité"
|
|||||||
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,7 +466,7 @@ 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:165 apps/note/tables.py:211
|
||||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||||
#: apps/wei/tables.py:74
|
#: apps/wei/tables.py:74
|
||||||
msgid "delete"
|
msgid "delete"
|
||||||
@ -850,33 +850,33 @@ msgstr "l'adhésion commence le"
|
|||||||
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:428
|
||||||
#, 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:437 apps/member/views.py:651
|
||||||
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:449 apps/member/views.py:660
|
||||||
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:502
|
||||||
#, 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:505 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:506
|
||||||
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"
|
||||||
|
|
||||||
@ -1535,7 +1535,7 @@ 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:169 apps/note/tables.py:213 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
|
||||||
@ -1556,6 +1556,10 @@ msgstr "Supprimer"
|
|||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Éditer"
|
msgstr "Éditer"
|
||||||
|
|
||||||
|
#: apps/note/tables.py:201 apps/note/tables.py:224
|
||||||
|
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 +1689,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"
|
||||||
@ -3501,8 +3517,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
|
||||||
|
|
||||||
@ -248,6 +259,8 @@ REST_FRAMEWORK = {
|
|||||||
# 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
|
||||||
|
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
@ -24,16 +24,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}">
|
<meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
{# Load CSS #}
|
{# Bootstrap, Font Awesome and custom CSS #}
|
||||||
<link rel="stylesheet" href="{% static "bootstrap4/css/bootstrap.min.css" %}">
|
<link rel="stylesheet" href="{% static "bootstrap4/css/bootstrap.min.css" %}">
|
||||||
|
<link rel="stylesheet" href="{% static "font-awesome/css/font-awesome.min.css" %}">
|
||||||
<link rel="stylesheet" href="{% static "css/custom.css" %}">
|
<link rel="stylesheet" href="{% static "css/custom.css" %}">
|
||||||
|
|
||||||
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
||||||
<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>
|
||||||
|
|
||||||
@ -63,101 +63,54 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if "note.transactiontemplate"|not_empty_model_list %}
|
{% if "note.transactiontemplate"|not_empty_model_list %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'note:consos' as url %}
|
{% url 'note:consos' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||||
<svg class="bi bi-mug" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M1 2a1 1 0 0 1 1-1h11a1 1 0 0 1 1 1v1h.5A1.5 1.5 0 0 1 16 4.5v7a1.5 1.5 0 0 1-1.5 1.5h-.55a2.5 2.5 0 0 1-2.45 2h-8A2.5 2.5 0 0 1 1 12.5V2zm13 10h.5a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.5-.5H14v8z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Consumptions' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated and user|is_member:"Kfet" %}
|
{% if user.is_authenticated and user|is_member:"Kfet" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'note:transfer' as url %}
|
{% url 'note:transfer' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %} </a>
|
||||||
<svg class="bi bi-exchange" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Transfer' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "auth.user"|model_list_length >= 2 %}
|
{% if "auth.user"|model_list_length >= 2 %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'member:user_list' as url %}
|
{% url 'member:user_list' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-user"></i> {% trans 'Users' %}</a>
|
||||||
<svg class="bi bi-user" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Users' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "member.club"|not_empty_model_list %}
|
{% if "member.club"|not_empty_model_list %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'member:club_list' as url %}
|
{% url 'member:club_list' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
||||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
|
|
||||||
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Clubs' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "activity.activity"|not_empty_model_list %}
|
{% if "activity.activity"|not_empty_model_list %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'activity:activity_list' as url %}
|
{% url 'activity:activity_list' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||||
<svg class="bi bi-calendar" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V5h16V4H0V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Activities' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "treasury.invoice"|not_empty_model_list %}
|
{% if "treasury.invoice"|not_empty_model_list %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'treasury:invoice_list' as url %}
|
{% url 'treasury:invoice_list' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-credit-card"></i> {% trans 'Treasury' %}</a>
|
||||||
<svg class="bi bi-credit-card" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1H0V4zm0 3v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7H0zm3 2h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Treasury' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "wei.weiclub"|not_empty_model_list %}
|
{% if "wei.weiclub"|not_empty_model_list %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'wei:current_wei_detail' as url %}
|
{% url 'wei:current_wei_detail' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-bus"></i> {% trans 'WEI' %}</a>
|
||||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'WEI' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'permission:rights' as url %}
|
{% url 'permission:rights' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-balance-scale"></i> {% trans 'Rights' %}</a>
|
||||||
<svg class="bi bi-shield" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M8 0c-.69 0-1.843.265-2.928.56-1.11.3-2.229.655-2.887.87a1.54 1.54 0 0 0-1.044 1.262c-.596 4.477.787 7.795 2.465 9.99a11.777 11.777 0 0 0 2.517 2.453c.386.273.744.482 1.048.625.28.132.581.24.829.24s.548-.108.829-.24a7.159 7.159 0 0 0 1.048-.625 11.775 11.775 0 0 0 2.517-2.453c1.678-2.195 3.061-5.513 2.465-9.99a1.541 1.541 0 0 0-1.044-1.263 62.467 62.467 0 0 0-2.887-.87C9.843.266 8.69 0 8 0zm0 5a1.5 1.5 0 0 1 .5 2.915l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99A1.5 1.5 0 0 1 8 5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Rights' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_staff and ""|has_perm:user %}
|
{% if request.user.is_staff and ""|has_perm:user %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a data-turbolinks="false" class="nav-link" href="{% url 'admin:index' %}">
|
<a data-turbolinks="false" class="nav-link" href="{% url 'admin:index' %}"><i class="fa fa-cogs"></i> {% trans 'Admin' %}</a>
|
||||||
<svg class="bi bi-cog" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans 'Admin' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -165,25 +118,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<svg class="bi bi-user" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-user"></i>
|
||||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
</svg>
|
|
||||||
<span id="user_balance">{{ request.user.username }} ({{ request.user.note.balance | pretty_money }})</span>
|
<span id="user_balance">{{ request.user.username }} ({{ request.user.note.balance | pretty_money }})</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right"
|
<div class="dropdown-menu dropdown-menu-right"
|
||||||
aria-labelledby="navbarDropdownMenuLink">
|
aria-labelledby="navbarDropdownMenuLink">
|
||||||
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
|
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
|
||||||
<svg class="bi bi-user" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-user"></i> {% trans "My account" %}
|
||||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "My account" %}
|
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item" href="{% url 'logout' %}">
|
<a class="dropdown-item" href="{% url 'logout' %}">
|
||||||
<svg class="bi bi-signout" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
|
||||||
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z"/>
|
|
||||||
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Log out" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -191,22 +135,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if request.path != "/registration/signup/" %}
|
{% if request.path != "/registration/signup/" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'registration:signup' %}">
|
<a class="nav-link" href="{% url 'registration:signup' %}">
|
||||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-user-plus"></i> {% trans "Sign up" %}
|
||||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Sign up" %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.path != "/accounts/login/" %}
|
{% if request.path != "/accounts/login/" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'login' %}">
|
<a class="nav-link" href="{% url 'login' %}">
|
||||||
<svg class="bi bi-login" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<i class="fa fa-sign-in"></i> {% trans "Log in" %}
|
||||||
<path fill-rule="evenodd" d="M6 3.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 0-1 0v2A1.5 1.5 0 0 0 6.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-8A1.5 1.5 0 0 0 5 3.5v2a.5.5 0 0 0 1 0v-2z"/>
|
|
||||||
<path fill-rule="evenodd" d="M11.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H1.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
|
|
||||||
</svg>
|
|
||||||
{% trans "Log in" %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Reference in New Issue
Block a user