mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 09:58:23 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
466cbd9878 |
@ -12,7 +12,7 @@ RUN apt-get update && \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||
python3-bs4 python3-setuptools \
|
||||
uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||
texlive-xetex gettext libjs-bootstrap4 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Instal PyPI requirements
|
||||
|
@ -1,8 +1,8 @@
|
||||
# NoteKfet 2020
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
||||
|
||||
## 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 install --no-install-recommends -y \
|
||||
ipython3 python3-setuptools python3-venv python3-dev \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||
texlive-xetex gettext libjs-bootstrap4 git
|
||||
```
|
||||
|
||||
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-bs4 python3-setuptools python3-docutils \
|
||||
memcached uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
||||
texlive-xetex gettext libjs-bootstrap4 \
|
||||
nginx python3-venv git acl
|
||||
```
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
prompt: "Password of the database (leave it blank to skip database init)"
|
||||
private: yes
|
||||
vars:
|
||||
mirror: eclats.crans.org
|
||||
mirror: mirror.crans.org
|
||||
roles:
|
||||
- 1-apt-basic
|
||||
- 2-nk20
|
||||
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
note:
|
||||
server_name: note.crans.org
|
||||
git_branch: main
|
||||
git_branch: master
|
||||
serve_static: true
|
||||
cron_enabled: true
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -1,15 +1,14 @@
|
||||
---
|
||||
- name: Add buster-backports to apt sources if needed
|
||||
- name: Add buster-backports to apt sources
|
||||
apt_repository:
|
||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||
state: present
|
||||
when:
|
||||
- ansible_distribution == "Debian"
|
||||
- ansible_distribution_major_version | int == 10
|
||||
when: ansible_facts['distribution'] == "Debian"
|
||||
|
||||
- name: Install note_kfet APT dependencies
|
||||
apt:
|
||||
update_cache: true
|
||||
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
||||
install_recommends: false
|
||||
name:
|
||||
# Common tools
|
||||
@ -18,7 +17,6 @@
|
||||
- ipython3
|
||||
|
||||
# Front-end dependencies
|
||||
- fonts-font-awesome
|
||||
- libjs-bootstrap4
|
||||
|
||||
# Python dependencies
|
||||
|
@ -38,7 +38,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</a>
|
||||
|
||||
<input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
|
||||
<button id="trigger" class="btn btn-secondary">Click me !</button>
|
||||
|
||||
<hr>
|
||||
|
||||
@ -64,46 +63,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
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) {
|
||||
let code = event.originalEvent.keyCode
|
||||
if (65 <= code <= 122 || code === 13) {
|
||||
debounce(reloadTable)()
|
||||
}
|
||||
if (code === 0)
|
||||
process_qrcode();
|
||||
});
|
||||
|
||||
$(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() {
|
||||
$(".table-row").click(function (e) {
|
||||
let target = e.target.parentElement;
|
||||
@ -200,4 +168,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -34,7 +34,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
<div class="card-footer">
|
||||
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
||||
<i class="fa fa-calendar-plus-o" aria-hidden="true"></i>
|
||||
<svg class="bi bi-calendar-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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' %}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -168,9 +168,6 @@ 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),
|
||||
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"])
|
||||
|
||||
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
||||
|
@ -7,11 +7,8 @@ from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from member.api.serializers import ProfileSerializer, MembershipSerializer
|
||||
from member.models import Membership
|
||||
from note.api.serializers import NoteSerializer
|
||||
from note.models import Alias
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
@ -48,30 +45,18 @@ class OAuthSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
normalized_name = serializers.SerializerMethodField()
|
||||
|
||||
profile = serializers.SerializerMethodField()
|
||||
profile = ProfileSerializer()
|
||||
|
||||
note = serializers.SerializerMethodField()
|
||||
note = NoteSerializer()
|
||||
|
||||
memberships = serializers.SerializerMethodField()
|
||||
|
||||
def get_normalized_name(self, obj):
|
||||
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):
|
||||
# Display only memberships that we are allowed to see.
|
||||
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
|
||||
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now())
|
||||
.filter(PermissionBackend.filter_queryset(get_current_request(), Membership, 'view')))
|
||||
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now()))
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
@ -1,18 +0,0 @@
|
||||
# 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,18 +258,16 @@ class Club(models.Model):
|
||||
This function is called each time the club detail view is displayed.
|
||||
Update the year of the membership dates.
|
||||
"""
|
||||
if not self.membership_start or not self.membership_end:
|
||||
if not self.membership_start:
|
||||
return
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
if (today - self.membership_start).days >= 365:
|
||||
if self.membership_start:
|
||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||
self.membership_start.month, self.membership_start.day)
|
||||
if self.membership_end:
|
||||
self.membership_end = datetime.date(self.membership_end.year + 1,
|
||||
self.membership_end.month, self.membership_end.day)
|
||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||
self.membership_start.month, self.membership_start.day)
|
||||
self.membership_end = datetime.date(self.membership_end.year + 1,
|
||||
self.membership_end.month, self.membership_end.day)
|
||||
self._force_save = True
|
||||
self.save(force_update=True)
|
||||
|
||||
|
@ -120,7 +120,7 @@ class MembershipTable(tables.Table):
|
||||
club=record.club,
|
||||
user=record.user,
|
||||
date_start__gte=record.club.membership_start,
|
||||
date_end__lte=record.club.membership_end or date(9999, 12, 31),
|
||||
date_end__lte=record.club.membership_end,
|
||||
).exists(): # If the renew is not yet performed
|
||||
empty_membership = Membership(
|
||||
club=record.club,
|
||||
|
@ -45,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-footer">
|
||||
{% if user_object %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
||||
{% if request.path_info != user_profile_url %}
|
||||
@ -59,7 +62,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if ".change_"|has_perm:club %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
||||
data-turbolinks="false">
|
||||
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% url 'member:club_detail' club.pk as club_detail_url %}
|
||||
|
@ -10,7 +10,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-users"></i> {% trans "Club managers" %}
|
||||
<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="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>
|
||||
</div>
|
||||
{% render_table managers %}
|
||||
@ -23,7 +26,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
||||
<i class="fa fa-users"></i> {% trans "Club members" %}
|
||||
<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 "Club members" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table member_list %}
|
||||
@ -37,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="historyListHeading">
|
||||
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@ -47,7 +47,9 @@
|
||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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 }})
|
||||
</a>
|
||||
</dd>
|
||||
|
@ -11,7 +11,9 @@
|
||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
||||
<i class="fa fa-lock"></i>
|
||||
<svg class="bi bi-lock" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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' %}
|
||||
</a>
|
||||
</dd>
|
||||
@ -20,7 +22,9 @@
|
||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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 }})
|
||||
</a>
|
||||
</dd>
|
||||
@ -52,10 +56,10 @@
|
||||
{% if user_object.pk == user.pk %}
|
||||
<div class="text-center">
|
||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||
<i class="fa fa-cogs"></i> {% trans 'API token' %}
|
||||
</a>
|
||||
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
|
||||
<i class="fa fa-qrcode"></i> {% trans 'QR Code' %}
|
||||
<svg class="bi bi-cogs" 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 'API token' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -18,7 +18,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-users"></i> {% trans "View my memberships" %}
|
||||
<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="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>
|
||||
</div>
|
||||
{% render_table club_list %}
|
||||
@ -29,7 +32,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
{% if "note.view_note"|has_perm:user_object.note %}
|
||||
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@ -1,36 +0,0 @@
|
||||
{% 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,7 +7,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% block content %}
|
||||
{% if can_manage_registrations %}
|
||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
@ -24,5 +24,4 @@ urlpatterns = [
|
||||
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('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_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.models import Alias, NoteUser, NoteClub
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
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)
|
||||
# Don't log these tests
|
||||
modified_note._no_signal = True
|
||||
modified_note.is_active = False
|
||||
modified_note.is_active = True
|
||||
modified_note.inactivity_reason = 'manual'
|
||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||
.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.save()
|
||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||
.check_perm(self.request, "note.change_note_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 user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||
.check_perm(self.request, "note.change_note_is_active", modified_note)
|
||||
|
||||
return context
|
||||
|
||||
@ -256,8 +256,7 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(
|
||||
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
|
||||
.order_by('normalized_name').all())
|
||||
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
@ -331,14 +330,6 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||
context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
|
||||
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 #
|
||||
@ -412,12 +403,9 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
club = self.object
|
||||
context["note"] = club.note
|
||||
|
||||
club = context["club"]
|
||||
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
|
||||
# managers list
|
||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||
date_start__lte=date.today(), date_end__gte=date.today())\
|
||||
@ -455,29 +443,6 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context["can_add_members"] = PermissionBackend()\
|
||||
.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
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
import html
|
||||
|
||||
import django_tables2 as tables
|
||||
from django.utils.html import format_html, mark_safe
|
||||
from django.utils.html import format_html
|
||||
from django_tables2.utils import A
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.middlewares import get_current_request
|
||||
@ -197,17 +197,6 @@ class ButtonTable(tables.Table):
|
||||
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,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}},
|
||||
@ -215,16 +204,3 @@ class ButtonTable(tables.Table):
|
||||
|
||||
def render_amount(self, 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,7 +129,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{# Mode switch #}
|
||||
<div class="card-footer border-primary">
|
||||
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
||||
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
||||
<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 %}
|
||||
<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() {
|
||||
let searchbar_obj = $("#search_field");
|
||||
let timer_on = false;
|
||||
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();
|
||||
|
||||
function reloadTable() {
|
||||
let pattern = searchbar_obj.val();
|
||||
$("#buttons_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + " #buttons_table", refreshMatchedWords);
|
||||
}
|
||||
|
||||
searchbar_obj.keyup(function() {
|
||||
if (timer_on)
|
||||
clearTimeout(timer);
|
||||
@ -77,28 +77,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
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>
|
||||
{% endblock %}
|
||||
|
@ -90,9 +90,9 @@ class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, Sing
|
||||
if "search" in self.request.GET:
|
||||
pattern = self.request.GET["search"]
|
||||
qs = qs.filter(
|
||||
Q(name__iregex=pattern)
|
||||
| Q(destination__club__name__iregex=pattern)
|
||||
| Q(category__name__iregex=pattern)
|
||||
Q(name__iregex="^" + pattern)
|
||||
| Q(destination__club__name__iregex="^" + pattern)
|
||||
| Q(category__name__iregex="^" + pattern)
|
||||
| Q(description__iregex=pattern)
|
||||
)
|
||||
|
||||
|
@ -977,7 +977,7 @@
|
||||
],
|
||||
"query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]",
|
||||
"type": "view",
|
||||
"mask": 2,
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir les transactions d'un club"
|
||||
@ -2511,7 +2511,7 @@
|
||||
"note",
|
||||
"noteuser"
|
||||
],
|
||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"inactivity_reason\": null}]]",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "is_active",
|
||||
@ -2527,7 +2527,7 @@
|
||||
"note",
|
||||
"noteuser"
|
||||
],
|
||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]",
|
||||
"query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"inactivity_reason\": null}]]",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "inactivity_reason",
|
||||
@ -2871,102 +2871,6 @@
|
||||
"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",
|
||||
"pk": 1,
|
||||
@ -2997,11 +2901,7 @@
|
||||
126,
|
||||
161,
|
||||
162,
|
||||
165,
|
||||
186,
|
||||
187,
|
||||
188,
|
||||
189
|
||||
165
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3110,9 +3010,7 @@
|
||||
166,
|
||||
167,
|
||||
168,
|
||||
182,
|
||||
184,
|
||||
185
|
||||
182
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3380,13 +3278,7 @@
|
||||
180,
|
||||
181,
|
||||
182,
|
||||
183,
|
||||
184,
|
||||
185,
|
||||
186,
|
||||
187,
|
||||
188,
|
||||
189
|
||||
183
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3446,8 +3338,7 @@
|
||||
45,
|
||||
46,
|
||||
148,
|
||||
149,
|
||||
182
|
||||
149
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from oauth2_provider.oauth2_validators import OAuth2Validator
|
||||
|
||||
from oauth2_provider.scopes import BaseScopes
|
||||
from member.models import Club
|
||||
from note_kfet.middlewares import get_current_request
|
||||
@ -32,26 +32,3 @@ class PermissionScopes(BaseScopes):
|
||||
return []
|
||||
return [f"{p.id}_{p.membership.club.id}"
|
||||
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">
|
||||
{% for app, app_scopes in scopes.items %}
|
||||
<div class="card">
|
||||
<div class="card-header" id="app-{{ app.name|slugify }}-title">
|
||||
<div class="card-header" id="app-{{ app.name.lower }}-title">
|
||||
<a class="text-decoration-none collapsed" href="#" data-toggle="collapse"
|
||||
data-target="#app-{{ app.name|slugify }}" aria-expanded="false"
|
||||
aria-controls="app-{{ app.name|slugify }}">
|
||||
data-target="#app-{{ app.name.lower }}" aria-expanded="false"
|
||||
aria-controls="app-{{ app.name.lower }}">
|
||||
{{ app.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse" id="app-{{ app.name|slugify }}" aria-labelledby="app-{{ app.name|slugify }}" data-target="#accordionApps">
|
||||
<div class="collapse" id="app-{{ app.name.lower }}" aria-labelledby="app-{{ app.name.lower }}" data-target="#accordionApps">
|
||||
<div class="card-body">
|
||||
{% for scope_id, scope_desc in app_scopes.items %}
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="scope-{{ app.name|slugify }}-{{ scope_id }}">
|
||||
<input type="checkbox" id="scope-{{ app.name|slugify }}-{{ scope_id }}"
|
||||
name="scope-{{ app.name|slugify }}" class="checkboxinput form-check-input" value="{{ scope_id }}">
|
||||
<label class="form-check-label" for="scope-{{ app.name.lower }}-{{ scope_id }}">
|
||||
<input type="checkbox" id="scope-{{ app.name.lower }}-{{ scope_id }}"
|
||||
name="scope-{{ app.name.lower }}" class="checkboxinput form-check-input" value="{{ scope_id }}">
|
||||
{{ scope_desc }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<p id="url-{{ app.name|slugify }}">
|
||||
<p id="url-{{ app.name.lower }}">
|
||||
<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
|
||||
</a>
|
||||
@ -51,10 +51,11 @@
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
{% for app in scopes.keys %}
|
||||
for (let element of document.getElementsByName("scope-{{ app.name|slugify }}")) {
|
||||
let elements = document.getElementsByName("scope-{{ app.name.lower }}");
|
||||
for (let element of elements) {
|
||||
element.onchange = function (event) {
|
||||
let scope = ""
|
||||
for (let element of document.getElementsByName("scope-{{ app.name|slugify }}")) {
|
||||
for (let element of elements) {
|
||||
if (element.checked) {
|
||||
scope += element.value + " "
|
||||
}
|
||||
@ -62,7 +63,7 @@
|
||||
|
||||
scope = scope.substr(0, scope.length - 1)
|
||||
|
||||
document.getElementById("url-{{ app.name|slugify }}").innerHTML = 'Scopes : ' + scope
|
||||
document.getElementById("url-{{ app.name.lower }}").innerHTML = 'Scopes : ' + scope
|
||||
+ '<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='
|
||||
+ scope.replaceAll(' ', '%20') + '</a>'
|
||||
|
Submodule apps/scripts updated: 86bc2d2698...7a022b9407
@ -1,18 +0,0 @@
|
||||
# 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
|
||||
|
||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm
|
||||
from .wei2022 import WEISurvey2022
|
||||
from .wei2021 import WEISurvey2021
|
||||
|
||||
|
||||
__all__ = [
|
||||
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
|
||||
]
|
||||
|
||||
CurrentSurvey = WEISurvey2022
|
||||
CurrentSurvey = WEISurvey2021
|
||||
|
@ -1,293 +0,0 @@
|
||||
# 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,7 +29,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||
<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 "Teams" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table teams %}
|
||||
@ -42,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Members" %}
|
||||
<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 "Members" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table memberships %}
|
||||
@ -51,7 +57,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<hr>
|
||||
|
||||
<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"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -47,7 +47,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||
<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 "Teams" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table memberships %}
|
||||
@ -57,7 +60,13 @@ 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 %}"
|
||||
data-turbolinks="false">
|
||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -48,7 +48,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card bg-white mb-3">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<span class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Buses" %}
|
||||
<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 "Buses" %}
|
||||
</span>
|
||||
</div>
|
||||
{% render_table buses %}
|
||||
@ -60,7 +63,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
href="{% url "wei:wei_memberships" pk=club.pk %}">
|
||||
<i class="fa fa-users"></i> {% trans "Members of the WEI" %}
|
||||
<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 "Members of the WEI" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table member_list %}
|
||||
@ -72,7 +80,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<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 %}
|
||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
@ -86,7 +97,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="historyListHeading">
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
href="{% url 'wei:wei_registrations' pk=club.pk %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Unvalidated registrations" %}
|
||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@ -28,7 +28,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</a>
|
||||
<hr>
|
||||
<a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false">
|
||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,7 +25,6 @@ class TestWEIAlgorithm(TestCase):
|
||||
email="wei2021@example.com",
|
||||
date_start='2021-09-17',
|
||||
date_end='2021-09-19',
|
||||
year=2021,
|
||||
)
|
||||
|
||||
self.buses = []
|
||||
|
@ -1,110 +0,0 @@
|
||||
# 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)
|
||||
|
||||
self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey)
|
||||
self.assertEqual(CurrentSurvey.get_year(), 2022)
|
||||
self.assertEqual(CurrentSurvey.get_year(), 2021)
|
||||
|
||||
|
||||
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
|
||||
présent à l'adresse suivante :
|
||||
`/templates/treasury/invoice_sample.tex <https://gitlab.crans.org/bde/nk20/-/tree/main/templates/treasury/invoice_sample.tex>`_
|
||||
`/templates/treasury/invoice_sample.tex <https://gitlab.crans.org/bde/nk20/-/tree/master/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
|
||||
ficher tex dans un dossier temporaire. On fait ensuite 2 appels à ``pdflatex`` pour générer la facture au format PDF.
|
||||
|
@ -41,14 +41,8 @@ On a ensuite besoin de définir nos propres scopes afin d'avoir des permissions
|
||||
|
||||
OAUTH2_PROVIDER = {
|
||||
'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`` :
|
||||
|
||||
.. code:: python
|
||||
@ -100,27 +94,6 @@ du format renvoyé.
|
||||
Vous pouvez donc contrôler le plus finement possible les permissions octroyées à vos
|
||||
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
|
||||
###################
|
||||
|
||||
@ -143,7 +116,6 @@ installées (sur votre propre client), puis de bien ajouter l'application social
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
'notekfet': {
|
||||
# 'DOMAIN': 'note.crans.org',
|
||||
'SCOPE': ['1_1', '2_1'],
|
||||
},
|
||||
...
|
||||
}
|
||||
@ -151,10 +123,6 @@ 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
|
||||
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
|
||||
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
|
||||
à adapter selon votre configuration.
|
||||
|
@ -23,7 +23,7 @@ Sur un Ubuntu/Debian :
|
||||
$ sudo apt update
|
||||
$ sudo apt install --no-install-recommends -y \
|
||||
python3-setuptools python3-venv python3-dev \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||
texlive-xetex gettext libjs-bootstrap4 git
|
||||
|
||||
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 install -t buster-backports --no-install-recommends \
|
||||
gettext git ipython3 \ # Dépendances basiques
|
||||
fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web
|
||||
libjs-bootstrap4 \ # Pour l'affichage web
|
||||
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
|
||||
python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
|
||||
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
|
||||
|
||||
Par défaut, le dépôt est configuré pour suivre la branche ``main``, qui est la branche
|
||||
Par défaut, le dépôt est configuré pour suivre la branche ``master``, qui est la 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
|
||||
`<https://note-dev.crans.org/>`_), on peut faire :
|
||||
@ -587,7 +587,7 @@ Dans ce fichier, remplissez :
|
||||
---
|
||||
note:
|
||||
server_name: note.crans.org
|
||||
git_branch: main
|
||||
git_branch: master
|
||||
cron_enabled: true
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-10-07 22:55+0200\n"
|
||||
"POT-Creation-Date: 2021-09-28 17:02+0200\n"
|
||||
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
|
||||
@ -279,7 +279,7 @@ msgstr "Prénom"
|
||||
msgid "Note"
|
||||
msgstr "Note"
|
||||
|
||||
#: apps/activity/tables.py:90 apps/member/tables.py:50
|
||||
#: apps/activity/tables.py:90 apps/member/tables.py:49
|
||||
msgid "Balance"
|
||||
msgstr "Solde du compte"
|
||||
|
||||
@ -320,13 +320,13 @@ msgstr "Entrées"
|
||||
msgid "Return to activity page"
|
||||
msgstr "Retour à la page de l'activité"
|
||||
|
||||
#: apps/activity/templates/activity/activity_entry.html:94
|
||||
#: apps/activity/templates/activity/activity_entry.html:129
|
||||
#: apps/activity/templates/activity/activity_entry.html:89
|
||||
#: apps/activity/templates/activity/activity_entry.html:124
|
||||
msgid "Entry done, but caution: the user is not a Kfet member."
|
||||
msgstr ""
|
||||
"Entrée effectuée, mais attention : la personne n'est pas un adhérent Kfet."
|
||||
|
||||
#: apps/activity/templates/activity/activity_entry.html:132
|
||||
#: apps/activity/templates/activity/activity_entry.html:127
|
||||
msgid "Entry done!"
|
||||
msgstr "Entrée effectuée !"
|
||||
|
||||
@ -404,33 +404,33 @@ msgstr "Créer une nouvelle activité"
|
||||
msgid "Activities"
|
||||
msgstr "Activités"
|
||||
|
||||
#: apps/activity/views.py:93
|
||||
#: apps/activity/views.py:95
|
||||
msgid "Activity detail"
|
||||
msgstr "Détails de l'activité"
|
||||
|
||||
#: apps/activity/views.py:113
|
||||
#: apps/activity/views.py:115
|
||||
msgid "Update activity"
|
||||
msgstr "Modifier l'activité"
|
||||
|
||||
#: apps/activity/views.py:140
|
||||
#: apps/activity/views.py:142
|
||||
msgid "Invite guest to the activity \"{}\""
|
||||
msgstr "Invitation pour l'activité « {} »"
|
||||
|
||||
#: apps/activity/views.py:178
|
||||
#: apps/activity/views.py:177
|
||||
msgid "You are not allowed to display the entry interface for this activity."
|
||||
msgstr ""
|
||||
"Vous n'êtes pas autorisé à afficher l'interface des entrées pour cette "
|
||||
"activité."
|
||||
|
||||
#: apps/activity/views.py:181
|
||||
#: apps/activity/views.py:180
|
||||
msgid "This activity does not support activity entries."
|
||||
msgstr "Cette activité ne requiert pas d'entrées."
|
||||
|
||||
#: apps/activity/views.py:184
|
||||
#: apps/activity/views.py:183
|
||||
msgid "This activity is closed."
|
||||
msgstr "Cette activité est fermée."
|
||||
|
||||
#: apps/activity/views.py:280
|
||||
#: apps/activity/views.py:279
|
||||
msgid "Entry for activity \"{}\""
|
||||
msgstr "Entrées pour l'activité « {} »"
|
||||
|
||||
@ -466,7 +466,7 @@ msgstr "nouvelles données"
|
||||
msgid "create"
|
||||
msgstr "créer"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:165 apps/note/tables.py:211
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:165 apps/note/tables.py:201
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
@ -850,33 +850,33 @@ msgstr "l'adhésion commence le"
|
||||
msgid "membership ends on"
|
||||
msgstr "l'adhésion finit le"
|
||||
|
||||
#: apps/member/models.py:428
|
||||
#: apps/member/models.py:422
|
||||
#, python-brace-format
|
||||
msgid "The role {role} does not apply to the club {club}."
|
||||
msgstr "Le rôle {role} ne s'applique pas au club {club}."
|
||||
|
||||
#: apps/member/models.py:437 apps/member/views.py:651
|
||||
#: apps/member/models.py:431 apps/member/views.py:651
|
||||
msgid "User is already a member of the club"
|
||||
msgstr "L'utilisateur est déjà membre du club"
|
||||
|
||||
#: apps/member/models.py:449 apps/member/views.py:660
|
||||
#: apps/member/models.py:443 apps/member/views.py:660
|
||||
msgid "User is not a member of the parent club"
|
||||
msgstr "L'utilisateur n'est pas membre du club parent"
|
||||
|
||||
#: apps/member/models.py:502
|
||||
#: apps/member/models.py:496
|
||||
#, python-brace-format
|
||||
msgid "Membership of {user} for the club {club}"
|
||||
msgstr "Adhésion de {user} pour le club {club}"
|
||||
|
||||
#: apps/member/models.py:505 apps/note/models/transactions.py:389
|
||||
#: apps/member/models.py:499 apps/note/models/transactions.py:389
|
||||
msgid "membership"
|
||||
msgstr "adhésion"
|
||||
|
||||
#: apps/member/models.py:506
|
||||
#: apps/member/models.py:500
|
||||
msgid "memberships"
|
||||
msgstr "adhésions"
|
||||
|
||||
#: apps/member/tables.py:139
|
||||
#: apps/member/tables.py:137
|
||||
msgid "Renew"
|
||||
msgstr "Renouveler"
|
||||
|
||||
@ -1535,7 +1535,7 @@ msgstr "Cliquez pour valider"
|
||||
msgid "No reason specified"
|
||||
msgstr "Pas de motif spécifié"
|
||||
|
||||
#: apps/note/tables.py:169 apps/note/tables.py:213 apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:169 apps/note/tables.py:203 apps/treasury/tables.py:39
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
||||
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
||||
@ -1556,10 +1556,6 @@ msgstr "Supprimer"
|
||||
msgid "Edit"
|
||||
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/transaction_form.html:48
|
||||
msgid "Please select a note"
|
||||
@ -1689,18 +1685,6 @@ msgstr "le bouton a bien été supprimé "
|
||||
msgid "Unable to delete button "
|
||||
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
|
||||
msgid "Transfer money"
|
||||
msgstr "Transférer de l'argent"
|
||||
@ -3517,3 +3501,8 @@ msgstr ""
|
||||
"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 "
|
||||
"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,8 +7,6 @@
|
||||
import os
|
||||
|
||||
# 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__))))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
@ -24,15 +22,6 @@ ALLOWED_HOSTS = [
|
||||
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
|
||||
|
||||
@ -259,8 +248,6 @@ REST_FRAMEWORK = {
|
||||
# OAuth2 Provider
|
||||
OAUTH2_PROVIDER = {
|
||||
'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
|
||||
|
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="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap, Font Awesome and custom CSS #}
|
||||
{# Load 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" %}">
|
||||
|
||||
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
||||
<script src="{% static "jquery/jquery.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 "js/turbolinks.js" %}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="{% static "js/base.js" %}"></script>
|
||||
<script src="{% static "js/konami.js" %}"></script>
|
||||
|
||||
@ -63,54 +63,101 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if "note.transactiontemplate"|not_empty_model_list %}
|
||||
<li class="nav-item">
|
||||
{% url 'note:consos' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated and user|is_member:"Kfet" %}
|
||||
<li class="nav-item">
|
||||
{% url 'note:transfer' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %} </a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "auth.user"|model_list_length >= 2 %}
|
||||
<li class="nav-item">
|
||||
{% url 'member:user_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-user"></i> {% trans 'Users' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "member.club"|not_empty_model_list %}
|
||||
<li class="nav-item">
|
||||
{% url 'member:club_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "activity.activity"|not_empty_model_list %}
|
||||
<li class="nav-item">
|
||||
{% url 'activity:activity_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "treasury.invoice"|not_empty_model_list %}
|
||||
<li class="nav-item">
|
||||
{% url 'treasury:invoice_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-credit-card"></i> {% trans 'Treasury' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "wei.weiclub"|not_empty_model_list %}
|
||||
<li class="nav-item">
|
||||
{% url 'wei:current_wei_detail' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-bus"></i> {% trans 'WEI' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
{% url 'permission:rights' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-balance-scale"></i> {% trans 'Rights' %}</a>
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if request.user.is_staff and ""|has_perm:user %}
|
||||
<li class="nav-item">
|
||||
<a data-turbolinks="false" class="nav-link" href="{% url 'admin:index' %}"><i class="fa fa-cogs"></i> {% trans 'Admin' %}</a>
|
||||
<a data-turbolinks="false" class="nav-link" href="{% url 'admin:index' %}">
|
||||
<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>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -118,16 +165,25 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-user"></i>
|
||||
<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>
|
||||
<span id="user_balance">{{ request.user.username }} ({{ request.user.note.balance | pretty_money }})</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
|
||||
<i class="fa fa-user"></i> {% trans "My account" %}
|
||||
<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 "My account" %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}">
|
||||
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
|
||||
<svg class="bi bi-signout" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</div>
|
||||
</li>
|
||||
@ -135,14 +191,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if request.path != "/registration/signup/" %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'registration:signup' %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Sign up" %}
|
||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.path != "/accounts/login/" %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'login' %}">
|
||||
<i class="fa fa-sign-in"></i> {% trans "Log in" %}
|
||||
<svg class="bi bi-login" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
Reference in New Issue
Block a user