mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-01 21:41:15 +02:00
Compare commits
150 Commits
9f0a22d3d1
...
svg_icons
Author | SHA1 | Date | |
---|---|---|---|
466cbd9878 | |||
0bd447b608 | |||
3f3c93d928 | |||
340c90f5d3 | |||
a05dfcbf3d
|
|||
ba3c0fb18d
|
|||
ab69963ea1 | |||
654c01631a
|
|||
d94cc2a7ad
|
|||
69bb38297f
|
|||
9628560d64
|
|||
df3bb71357
|
|||
2a216fd994
|
|||
8dd2619013
|
|||
62431a4910
|
|||
946bc1e497 | |||
d4896bfd76
|
|||
23f46cc598
|
|||
d1a9f21b56 | |||
d809b2595a
|
|||
97803ac983 | |||
b951c4aa05 | |||
69b3d2ac9c
|
|||
f29054558a
|
|||
11dd8adbb7 | |||
d437f2bdbd
|
|||
ac8453b04c
|
|||
6b4d18f4b3 | |||
668cfa71a7 | |||
161db0b00b
|
|||
8638c16b34
|
|||
9583cec3ff
|
|||
1ef25924a0
|
|||
e89383e3f4
|
|||
79a116d9c6
|
|||
aa75ce5c7a
|
|||
a3a9dfc812
|
|||
76531595ad
|
|||
a0b920ac94
|
|||
ab2e580e68
|
|||
0234f19a33
|
|||
1a4b7c83e8
|
|||
4c17e2a92b
|
|||
e68afc7d0a
|
|||
c6e3b54f94
|
|||
7e6a14296a | |||
780f78b385 | |||
4e3c32eb5e
|
|||
ef118c2445
|
|||
600ba15faa
|
|||
944bb127e2
|
|||
f6d042c998
|
|||
bb9a0a2593
|
|||
61feac13c7
|
|||
81e708a7e3
|
|||
3532846c87
|
|||
49551e88f8
|
|||
db936bf75a
|
|||
5828a20383 | |||
cea3138daf | |||
fb98d9cd8b
|
|||
0dd3da5c01
|
|||
af4be98b5b
|
|||
be6059eba6
|
|||
5793b83de7
|
|||
2c02c747f4
|
|||
a78f3b7caa
|
|||
1ee40cb94e
|
|||
bd035744a4
|
|||
7edd622755
|
|||
8fd5b6ee01
|
|||
03411ac9bd
|
|||
d965732b65
|
|||
048266ed61
|
|||
b27341009e
|
|||
da1e15c5e6
|
|||
4b03a78ad6
|
|||
fb6e3c3de0
|
|||
391f3bde8f
|
|||
ad04e45992
|
|||
4e1ba1447a
|
|||
b646f549d6
|
|||
ba9ef0371a
|
|||
881cd88f48
|
|||
b4ed354b73 | |||
e5051ab018
|
|||
bb69627ac5
|
|||
ffaa020310
|
|||
6d2b7054e2
|
|||
d888d5863a
|
|||
dbc7b3444b
|
|||
f25eb1d2c5
|
|||
a2a749e1ca
|
|||
5bf6a5501d
|
|||
9523b5f05f
|
|||
5eb3ffca66 | |||
9930c48253 | |||
d902e63a0c
|
|||
48b0bade51
|
|||
f75dbc4525
|
|||
fbf64db16e
|
|||
a3fd8ba063
|
|||
9b26207515
|
|||
7ea36a5415
|
|||
898f6d52bf
|
|||
8be16e7b58
|
|||
ea092803d7
|
|||
5e9f36ef1a
|
|||
b4d87bc6b5
|
|||
dd639d829e
|
|||
7b809ff3a6 | |||
d36edfc063
|
|||
cf87da096f
|
|||
e452b7acbf
|
|||
74ab4df9fe
|
|||
451851c955
|
|||
789ca149af | |||
7d3f1930b8 | |||
e8f4ca1e09
|
|||
733f145be3
|
|||
48c37353ea
|
|||
8056dc096d
|
|||
6d5b69cd26
|
|||
a7bdffd71a
|
|||
0887e4bbde
|
|||
199f4ca1f2
|
|||
802a6c68cb
|
|||
41a0b3a1c1
|
|||
aa35724be2
|
|||
9086d33158
|
|||
43d214b982
|
|||
b93e4a8d11
|
|||
b9a9704061
|
|||
fee52f326a
|
|||
317966d5c1 | |||
a5ecdd100c | |||
d5ecb72a71
|
|||
0b4b6dcb3e | |||
0d5f6c0332 | |||
7b28938cde
|
|||
35ffb36fbd
|
|||
08ba0b263a | |||
c4c4e9594f | |||
4166823d55 | |||
dc0f3dbcef | |||
4583958f50 | |||
b3abe9ab18 | |||
27f23b48b6 | |||
67e170d4a6 | |||
bab394908d |
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,6 +47,7 @@ backups/
|
|||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
shell.nix
|
||||||
|
|
||||||
# ansibles customs host
|
# ansibles customs host
|
||||||
ansible/host_vars/*.yaml
|
ansible/host_vars/*.yaml
|
||||||
|
@ -12,7 +12,7 @@ RUN apt-get update && \
|
|||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools \
|
python3-bs4 python3-setuptools \
|
||||||
uwsgi uwsgi-plugin-python3 \
|
uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
texlive-xetex gettext libjs-bootstrap4 && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Instal PyPI requirements
|
# Instal PyPI requirements
|
||||||
|
@ -23,7 +23,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
|
|||||||
$ sudo apt update
|
$ sudo apt update
|
||||||
$ sudo apt install --no-install-recommends -y \
|
$ sudo apt install --no-install-recommends -y \
|
||||||
ipython3 python3-setuptools python3-venv python3-dev \
|
ipython3 python3-setuptools python3-venv python3-dev \
|
||||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
texlive-xetex gettext libjs-bootstrap4 git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Clonage du dépot** là où vous voulez :
|
2. **Clonage du dépot** là où vous voulez :
|
||||||
@ -115,7 +115,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
|||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools python3-docutils \
|
python3-bs4 python3-setuptools python3-docutils \
|
||||||
memcached uwsgi uwsgi-plugin-python3 \
|
memcached uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
texlive-xetex gettext libjs-bootstrap4 \
|
||||||
nginx python3-venv git acl
|
nginx python3-venv git acl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
note:
|
|
||||||
server_name: note-beta.crans.org
|
|
||||||
git_branch: beta
|
|
||||||
cron_enabled: false
|
|
||||||
email: notekfet2020@lists.crans.org
|
|
@ -2,5 +2,6 @@
|
|||||||
note:
|
note:
|
||||||
server_name: note-dev.crans.org
|
server_name: note-dev.crans.org
|
||||||
git_branch: beta
|
git_branch: beta
|
||||||
|
serve_static: false
|
||||||
cron_enabled: false
|
cron_enabled: false
|
||||||
email: notekfet2020@lists.crans.org
|
email: notekfet2020@lists.crans.org
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
note:
|
note:
|
||||||
server_name: note.crans.org
|
server_name: note.crans.org
|
||||||
git_branch: master
|
git_branch: master
|
||||||
|
serve_static: true
|
||||||
cron_enabled: true
|
cron_enabled: true
|
||||||
email: notekfet2020@lists.crans.org
|
email: notekfet2020@lists.crans.org
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
[dev]
|
[dev]
|
||||||
bde-note-dev.adh.crans.org
|
bde-note-dev.adh.crans.org
|
||||||
bde-nk20-beta.adh.crans.org
|
|
||||||
|
|
||||||
[prod]
|
[prod]
|
||||||
bde-note.adh.crans.org
|
bde-note.adh.crans.org
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
- ipython3
|
- ipython3
|
||||||
|
|
||||||
# Front-end dependencies
|
# Front-end dependencies
|
||||||
- fonts-font-awesome
|
|
||||||
- libjs-bootstrap4
|
- libjs-bootstrap4
|
||||||
|
|
||||||
# Python dependencies
|
# Python dependencies
|
||||||
|
@ -41,6 +41,7 @@ server {
|
|||||||
# max upload size
|
# max upload size
|
||||||
client_max_body_size 75M; # adjust to taste
|
client_max_body_size 75M; # adjust to taste
|
||||||
|
|
||||||
|
{% if note.serve_static %}
|
||||||
# Django media
|
# Django media
|
||||||
location /media {
|
location /media {
|
||||||
alias /var/www/note_kfet/media; # your Django project's media files - amend as required
|
alias /var/www/note_kfet/media; # your Django project's media files - amend as required
|
||||||
@ -50,6 +51,7 @@ server {
|
|||||||
alias /var/www/note_kfet/static; # your Django project's static files - amend as required
|
alias /var/www/note_kfet/static; # your Django project's static files - amend as required
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
location /doc {
|
location /doc {
|
||||||
alias /var/www/documentation; # The documentation of the project
|
alias /var/www/documentation; # The documentation of the project
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'activity.apps.ActivityConfig'
|
default_app_config = 'activity.apps.ActivityConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.viewsets import ReadProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from member.models import Club
|
from member.models import Club
|
||||||
from note.models import Note, NoteUser
|
from note.models import Note, NoteUser
|
||||||
from note_kfet.inputs import Autocomplete, DateTimePickerInput
|
from note_kfet.inputs import Autocomplete, DateTimePickerInput
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .models import Activity, Guest
|
from .models import Activity, Guest
|
||||||
@ -24,10 +24,16 @@ class ActivityForm(forms.ModelForm):
|
|||||||
self.fields["attendees_club"].initial = Club.objects.get(name="Kfet")
|
self.fields["attendees_club"].initial = Club.objects.get(name="Kfet")
|
||||||
self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet"
|
self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet"
|
||||||
clubs = list(Club.objects.filter(PermissionBackend
|
clubs = list(Club.objects.filter(PermissionBackend
|
||||||
.filter_queryset(get_current_authenticated_user(), Club, "view")).all())
|
.filter_queryset(get_current_request(), Club, "view")).all())
|
||||||
shuffle(clubs)
|
shuffle(clubs)
|
||||||
self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
|
self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
|
||||||
|
|
||||||
|
def clean_organizer(self):
|
||||||
|
organizer = self.cleaned_data['organizer']
|
||||||
|
if not organizer.note.is_active:
|
||||||
|
self.add_error('organiser', _('The note of this club is inactive.'))
|
||||||
|
return organizer
|
||||||
|
|
||||||
def clean_date_end(self):
|
def clean_date_end(self):
|
||||||
date_end = self.cleaned_data["date_end"]
|
date_end = self.cleaned_data["date_end"]
|
||||||
date_start = self.cleaned_data["date_start"]
|
date_start = self.cleaned_data["date_start"]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import format_html
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_tables2 import A
|
from django_tables2 import A
|
||||||
@ -52,8 +54,8 @@ class GuestTable(tables.Table):
|
|||||||
def render_entry(self, record):
|
def render_entry(self, record):
|
||||||
if record.has_entry:
|
if record.has_entry:
|
||||||
return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
|
return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
|
||||||
return format_html('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> '
|
return mark_safe('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> '
|
||||||
'{delete_trans}</button>'.format(id=record.id, delete_trans=_("remove").capitalize()))
|
'{delete_trans}</button>'.format(id=record.id, delete_trans=_("remove").capitalize()))
|
||||||
|
|
||||||
|
|
||||||
def get_row_class(record):
|
def get_row_class(record):
|
||||||
@ -91,7 +93,7 @@ class EntryTable(tables.Table):
|
|||||||
if hasattr(record, 'username'):
|
if hasattr(record, 'username'):
|
||||||
username = record.username
|
username = record.username
|
||||||
if username != value:
|
if username != value:
|
||||||
return format_html(value + " <em>aka.</em> " + username)
|
return mark_safe(escape(value) + " <em>aka.</em> " + escape(username))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def render_balance(self, value):
|
def render_balance(self, value):
|
||||||
|
@ -63,7 +63,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
refreshBalance();
|
refreshBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
alias_obj.keyup(reloadTable);
|
alias_obj.keyup(function(event) {
|
||||||
|
let code = event.originalEvent.keyCode
|
||||||
|
if (65 <= code <= 122 || code === 13) {
|
||||||
|
debounce(reloadTable)()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).ready(init);
|
$(document).ready(init);
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
||||||
<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' %}
|
{% trans 'New activity' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
@ -66,21 +66,19 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
|
|||||||
ordering = ('-date_start',)
|
ordering = ('-date_start',)
|
||||||
extra_context = {"title": _("Activities")}
|
extra_context = {"title": _("Activities")}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self, **kwargs):
|
||||||
return super().get_queryset().distinct()
|
return super().get_queryset(**kwargs).distinct()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
|
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
|
||||||
context['upcoming'] = ActivityTable(
|
context['upcoming'] = ActivityTable(
|
||||||
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
|
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
|
||||||
prefix='upcoming-',
|
prefix='upcoming-',
|
||||||
)
|
)
|
||||||
|
|
||||||
started_activities = Activity.objects\
|
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
|
||||||
.filter(open=True, valid=True).all()
|
|
||||||
context["started_activities"] = started_activities
|
context["started_activities"] = started_activities
|
||||||
|
|
||||||
return context
|
return context
|
||||||
@ -98,7 +96,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context = super().get_context_data()
|
context = super().get_context_data()
|
||||||
|
|
||||||
table = GuestTable(data=Guest.objects.filter(activity=self.object)
|
table = GuestTable(data=Guest.objects.filter(activity=self.object)
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
|
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view")))
|
||||||
context["guests"] = table
|
context["guests"] = table
|
||||||
|
|
||||||
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
|
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
|
||||||
@ -144,15 +142,15 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
|
||||||
.get(pk=self.kwargs["pk"])
|
.filter(pk=self.kwargs["pk"]).first()
|
||||||
form.fields["inviter"].initial = self.request.user.note
|
form.fields["inviter"].initial = self.request.user.note
|
||||||
return form
|
return form
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.activity = Activity.objects\
|
form.instance.activity = Activity.objects\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
|
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")).get(pk=self.kwargs["pk"])
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
@ -173,7 +171,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
||||||
|
|
||||||
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
||||||
if not PermissionBackend.check_perm(self.request.user, "activity.add_entry", sample_entry):
|
if not PermissionBackend.check_perm(self.request, "activity.add_entry", sample_entry):
|
||||||
raise PermissionDenied(_("You are not allowed to display the entry interface for this activity."))
|
raise PermissionDenied(_("You are not allowed to display the entry interface for this activity."))
|
||||||
|
|
||||||
if not activity.activity_type.manage_entries:
|
if not activity.activity_type.manage_entries:
|
||||||
@ -191,8 +189,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
guest_qs = Guest.objects\
|
guest_qs = Guest.objects\
|
||||||
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
|
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
|
||||||
.filter(activity=activity)\
|
.filter(activity=activity)\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))\
|
||||||
.order_by('last_name', 'first_name').distinct()
|
.order_by('last_name', 'first_name')
|
||||||
|
|
||||||
if "search" in self.request.GET and self.request.GET["search"]:
|
if "search" in self.request.GET and self.request.GET["search"]:
|
||||||
pattern = self.request.GET["search"]
|
pattern = self.request.GET["search"]
|
||||||
@ -206,7 +204,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
guest_qs = guest_qs.none()
|
guest_qs = guest_qs.none()
|
||||||
return guest_qs
|
return guest_qs.distinct()
|
||||||
|
|
||||||
def get_invited_note(self, activity):
|
def get_invited_note(self, activity):
|
||||||
"""
|
"""
|
||||||
@ -230,7 +228,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Filter with permission backend
|
# Filter with permission backend
|
||||||
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view"))
|
||||||
|
|
||||||
if "search" in self.request.GET and self.request.GET["search"]:
|
if "search" in self.request.GET and self.request.GET["search"]:
|
||||||
pattern = self.request.GET["search"]
|
pattern = self.request.GET["search"]
|
||||||
@ -256,7 +254,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
"""
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
|
||||||
.distinct().get(pk=self.kwargs["pk"])
|
.distinct().get(pk=self.kwargs["pk"])
|
||||||
context["activity"] = activity
|
context["activity"] = activity
|
||||||
|
|
||||||
@ -281,9 +279,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
||||||
|
|
||||||
activities_open = Activity.objects.filter(open=True).filter(
|
activities_open = Activity.objects.filter(open=True).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
|
||||||
context["activities_open"] = [a for a in activities_open
|
context["activities_open"] = [a for a in activities_open
|
||||||
if PermissionBackend.check_perm(self.request.user,
|
if PermissionBackend.check_perm(self.request,
|
||||||
"activity.add_entry",
|
"activity.add_entry",
|
||||||
Entry(activity=a, note=self.request.user.note,))]
|
Entry(activity=a, note=self.request.user.note,))]
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'api.apps.APIConfig'
|
default_app_config = 'api.apps.APIConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -9,7 +9,6 @@ from django.contrib.auth.models import User
|
|||||||
from rest_framework.filters import SearchFilter
|
from rest_framework.filters import SearchFilter
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from note_kfet.middlewares import get_current_session
|
|
||||||
from note.models import Alias
|
from note.models import Alias
|
||||||
|
|
||||||
from .serializers import UserSerializer, ContentTypeSerializer
|
from .serializers import UserSerializer, ContentTypeSerializer
|
||||||
@ -25,9 +24,7 @@ class ReadProtectedModelViewSet(ModelViewSet):
|
|||||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct()
|
||||||
get_current_session().setdefault("permission_mask", 42)
|
|
||||||
return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet):
|
class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet):
|
||||||
@ -40,9 +37,7 @@ class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet):
|
|||||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct()
|
||||||
get_current_session().setdefault("permission_mask", 42)
|
|
||||||
return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(ReadProtectedModelViewSet):
|
class UserViewSet(ReadProtectedModelViewSet):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'logs.apps.LogsConfig'
|
default_app_config = 'logs.apps.LogsConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ChangelogViewSet
|
from .views import ChangelogViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from note.models import NoteUser, Alias
|
from note.models import NoteUser, Alias
|
||||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_ip
|
from note_kfet.middlewares import get_current_request
|
||||||
|
|
||||||
from .models import Changelog
|
from .models import Changelog
|
||||||
|
|
||||||
@ -57,9 +57,9 @@ def save_object(sender, instance, **kwargs):
|
|||||||
previous = instance._previous
|
previous = instance._previous
|
||||||
|
|
||||||
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||||
user, ip = get_current_authenticated_user(), get_current_ip()
|
request = get_current_request()
|
||||||
|
|
||||||
if user is None:
|
if request is None:
|
||||||
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||||
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||||
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||||
@ -71,9 +71,23 @@ def save_object(sender, instance, **kwargs):
|
|||||||
# else:
|
# else:
|
||||||
if note.exists():
|
if note.exists():
|
||||||
user = note.get().user
|
user = note.get().user
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
else:
|
||||||
|
user = request.user
|
||||||
|
if 'HTTP_X_REAL_IP' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_REAL_IP')
|
||||||
|
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
|
||||||
|
if not user.is_authenticated:
|
||||||
|
# For registration and OAuth2 purposes
|
||||||
|
user = None
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
if user is not None and instance._meta.label_lower == "auth.user" and previous:
|
if request is not None and instance._meta.label_lower == "auth.user" and previous:
|
||||||
# On n'enregistre pas les connexions
|
# On n'enregistre pas les connexions
|
||||||
if instance.last_login != previous.last_login:
|
if instance.last_login != previous.last_login:
|
||||||
return
|
return
|
||||||
@ -121,9 +135,9 @@ def delete_object(sender, instance, **kwargs):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||||
user, ip = get_current_authenticated_user(), get_current_ip()
|
request = get_current_request()
|
||||||
|
|
||||||
if user is None:
|
if request is None:
|
||||||
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||||
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||||
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||||
@ -135,6 +149,20 @@ def delete_object(sender, instance, **kwargs):
|
|||||||
# else:
|
# else:
|
||||||
if note.exists():
|
if note.exists():
|
||||||
user = note.get().user
|
user = note.get().user
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
else:
|
||||||
|
user = request.user
|
||||||
|
if 'HTTP_X_REAL_IP' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_REAL_IP')
|
||||||
|
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
|
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
|
||||||
|
if not user.is_authenticated:
|
||||||
|
# For registration and OAuth2 purposes
|
||||||
|
user = None
|
||||||
|
|
||||||
# On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
|
# On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
|
||||||
class CustomSerializer(ModelSerializer):
|
class CustomSerializer(ModelSerializer):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'member.apps.MemberConfig'
|
default_app_config = 'member.apps.MemberConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import PBKDF2PasswordHasher
|
from django.contrib.auth.hashers import PBKDF2PasswordHasher, mask_hash
|
||||||
from django.utils.crypto import constant_time_compare
|
from django.utils.crypto import constant_time_compare
|
||||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from note_kfet.middlewares import get_current_request
|
||||||
|
|
||||||
|
|
||||||
class CustomNK15Hasher(PBKDF2PasswordHasher):
|
class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||||
@ -24,16 +26,22 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
|
|||||||
|
|
||||||
def must_update(self, encoded):
|
def must_update(self, encoded):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
current_user = get_current_authenticated_user()
|
# Small hack to let superusers to impersonate people.
|
||||||
|
# Don't change their password.
|
||||||
|
request = get_current_request()
|
||||||
|
current_user = request.user
|
||||||
if current_user is not None and current_user.is_superuser:
|
if current_user is not None and current_user.is_superuser:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
current_user = get_current_authenticated_user()
|
# Small hack to let superusers to impersonate people.
|
||||||
|
# If a superuser is already connected, let him/her log in as another person.
|
||||||
|
request = get_current_request()
|
||||||
|
current_user = request.user
|
||||||
if current_user is not None and current_user.is_superuser\
|
if current_user is not None and current_user.is_superuser\
|
||||||
and get_current_session().get("permission_mask", -1) >= 42:
|
and request.session.get("permission_mask", -1) >= 42:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if '|' in encoded:
|
if '|' in encoded:
|
||||||
@ -41,6 +49,18 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
|
|||||||
return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
|
return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
|
||||||
return super().verify(password, encoded)
|
return super().verify(password, encoded)
|
||||||
|
|
||||||
|
def safe_summary(self, encoded):
|
||||||
|
# Displayed information in Django Admin.
|
||||||
|
if '|' in encoded:
|
||||||
|
salt, db_hashed_pass = encoded.split('$')[2].split('|')
|
||||||
|
return OrderedDict([
|
||||||
|
(_('algorithm'), 'custom_nk15'),
|
||||||
|
(_('iterations'), '1'),
|
||||||
|
(_('salt'), mask_hash(salt)),
|
||||||
|
(_('hash'), mask_hash(db_hashed_pass)),
|
||||||
|
])
|
||||||
|
return super().safe_summary(encoded)
|
||||||
|
|
||||||
|
|
||||||
class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
||||||
"""
|
"""
|
||||||
@ -51,8 +71,11 @@ class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
|||||||
|
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
current_user = get_current_authenticated_user()
|
# Small hack to let superusers to impersonate people.
|
||||||
|
# If a superuser is already connected, let him/her log in as another person.
|
||||||
|
request = get_current_request()
|
||||||
|
current_user = request.user
|
||||||
if current_user is not None and current_user.is_superuser\
|
if current_user is not None and current_user.is_superuser\
|
||||||
and get_current_session().get("permission_mask", -1) >= 42:
|
and request.session.get("permission_mask", -1) >= 42:
|
||||||
return True
|
return True
|
||||||
return super().verify(password, encoded)
|
return super().verify(password, encoded)
|
||||||
|
@ -19,8 +19,8 @@ def create_bde_and_kfet(apps, schema_editor):
|
|||||||
membership_fee_paid=500,
|
membership_fee_paid=500,
|
||||||
membership_fee_unpaid=500,
|
membership_fee_unpaid=500,
|
||||||
membership_duration=396,
|
membership_duration=396,
|
||||||
membership_start="2020-08-01",
|
membership_start="2021-08-01",
|
||||||
membership_end="2021-09-30",
|
membership_end="2022-09-30",
|
||||||
)
|
)
|
||||||
Club.objects.get_or_create(
|
Club.objects.get_or_create(
|
||||||
id=2,
|
id=2,
|
||||||
@ -31,8 +31,8 @@ def create_bde_and_kfet(apps, schema_editor):
|
|||||||
membership_fee_paid=3500,
|
membership_fee_paid=3500,
|
||||||
membership_fee_unpaid=3500,
|
membership_fee_unpaid=3500,
|
||||||
membership_duration=396,
|
membership_duration=396,
|
||||||
membership_start="2020-08-01",
|
membership_start="2021-08-01",
|
||||||
membership_end="2021-09-30",
|
membership_end="2022-09-30",
|
||||||
)
|
)
|
||||||
|
|
||||||
NoteClub.objects.get_or_create(
|
NoteClub.objects.get_or_create(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@ -57,7 +57,7 @@ class Profile(models.Model):
|
|||||||
('A1', _("Mathematics (A1)")),
|
('A1', _("Mathematics (A1)")),
|
||||||
('A2', _("Physics (A2)")),
|
('A2', _("Physics (A2)")),
|
||||||
("A'2", _("Applied physics (A'2)")),
|
("A'2", _("Applied physics (A'2)")),
|
||||||
('A''2', _("Chemistry (A''2)")),
|
("A''2", _("Chemistry (A''2)")),
|
||||||
('A3', _("Biology (A3)")),
|
('A3', _("Biology (A3)")),
|
||||||
('B1234', _("SAPHIRE (B1234)")),
|
('B1234', _("SAPHIRE (B1234)")),
|
||||||
('B1', _("Mechanics (B1)")),
|
('B1', _("Mechanics (B1)")),
|
||||||
@ -74,7 +74,7 @@ class Profile(models.Model):
|
|||||||
|
|
||||||
promotion = models.PositiveSmallIntegerField(
|
promotion = models.PositiveSmallIntegerField(
|
||||||
null=True,
|
null=True,
|
||||||
default=datetime.date.today().year,
|
default=datetime.date.today().year if datetime.date.today().month >= 8 else datetime.date.today().year - 1,
|
||||||
verbose_name=_("promotion"),
|
verbose_name=_("promotion"),
|
||||||
help_text=_("Year of entry to the school (None if not ENS student)"),
|
help_text=_("Year of entry to the school (None if not ENS student)"),
|
||||||
)
|
)
|
||||||
@ -413,6 +413,12 @@ class Membership(models.Model):
|
|||||||
"""
|
"""
|
||||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||||
"""
|
"""
|
||||||
|
# Ensure that club membership dates are valid
|
||||||
|
old_membership_start = self.club.membership_start
|
||||||
|
self.club.update_membership_dates()
|
||||||
|
if self.club.membership_start != old_membership_start:
|
||||||
|
self.club.save()
|
||||||
|
|
||||||
created = not self.pk
|
created = not self.pk
|
||||||
if not created:
|
if not created:
|
||||||
for role in self.roles.all():
|
for role in self.roles.all():
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from note.templatetags.pretty_money import pretty_money
|
from note.templatetags.pretty_money import pretty_money
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .models import Club, Membership
|
from .models import Club, Membership
|
||||||
@ -31,7 +31,8 @@ class ClubTable(tables.Table):
|
|||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
'id': lambda record: "row-" + str(record.pk),
|
'id': lambda record: "row-" + str(record.pk),
|
||||||
'data-href': lambda record: record.pk
|
'data-href': lambda record: record.pk,
|
||||||
|
'style': 'cursor:pointer',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -51,19 +52,19 @@ class UserTable(tables.Table):
|
|||||||
def render_email(self, record, value):
|
def render_email(self, record, value):
|
||||||
# Replace the email by a dash if the user can't see the profile detail
|
# Replace the email by a dash if the user can't see the profile detail
|
||||||
# Replace also the URL
|
# Replace also the URL
|
||||||
if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile):
|
if not PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile):
|
||||||
value = "—"
|
value = "—"
|
||||||
record.email = value
|
record.email = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def render_section(self, record, value):
|
def render_section(self, record, value):
|
||||||
return value \
|
return value \
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \
|
if PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile) \
|
||||||
else "—"
|
else "—"
|
||||||
|
|
||||||
def render_balance(self, record, value):
|
def render_balance(self, record, value):
|
||||||
return pretty_money(value)\
|
return pretty_money(value)\
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—"
|
if PermissionBackend.check_perm(get_current_request(), "note.view_note", record.note) else "—"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
@ -74,7 +75,8 @@ class UserTable(tables.Table):
|
|||||||
model = User
|
model = User
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
'data-href': lambda record: record.pk
|
'data-href': lambda record: record.pk,
|
||||||
|
'style': 'cursor:pointer',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,7 +95,7 @@ class MembershipTable(tables.Table):
|
|||||||
def render_user(self, value):
|
def render_user(self, value):
|
||||||
# If the user has the right, link the displayed user with the page of its detail.
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
s = value.username
|
s = value.username
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value):
|
||||||
s = format_html("<a href={url}>{name}</a>",
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
|
||||||
@ -102,7 +104,7 @@ class MembershipTable(tables.Table):
|
|||||||
def render_club(self, value):
|
def render_club(self, value):
|
||||||
# If the user has the right, link the displayed club with the page of its detail.
|
# If the user has the right, link the displayed club with the page of its detail.
|
||||||
s = value.name
|
s = value.name
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value):
|
if PermissionBackend.check_perm(get_current_request(), "member.view_club", value):
|
||||||
s = format_html("<a href={url}>{name}</a>",
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
|
||||||
@ -127,7 +129,7 @@ class MembershipTable(tables.Table):
|
|||||||
date_end=date.today(),
|
date_end=date.today(),
|
||||||
fee=0,
|
fee=0,
|
||||||
)
|
)
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
if PermissionBackend.check_perm(get_current_request(),
|
||||||
"member.add_membership", empty_membership): # If the user has right
|
"member.add_membership", empty_membership): # If the user has right
|
||||||
renew_url = reverse_lazy('member:club_renew_membership',
|
renew_url = reverse_lazy('member:club_renew_membership',
|
||||||
kwargs={"pk": record.pk})
|
kwargs={"pk": record.pk})
|
||||||
@ -142,7 +144,7 @@ class MembershipTable(tables.Table):
|
|||||||
# If the user has the right to manage the roles, display the link to manage them
|
# If the user has the right to manage the roles, display the link to manage them
|
||||||
roles = record.roles.all()
|
roles = record.roles.all()
|
||||||
s = ", ".join(str(role) for role in roles)
|
s = ", ".join(str(role) for role in roles)
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record):
|
if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record):
|
||||||
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
||||||
+ "'>" + s + "</a>")
|
+ "'>" + s + "</a>")
|
||||||
return s
|
return s
|
||||||
@ -165,7 +167,7 @@ class ClubManagerTable(tables.Table):
|
|||||||
def render_user(self, value):
|
def render_user(self, value):
|
||||||
# If the user has the right, link the displayed user with the page of its detail.
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
s = value.username
|
s = value.username
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value):
|
||||||
s = format_html("<a href={url}>{name}</a>",
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
|
||||||
|
@ -45,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
{% if user_object %}
|
{% if user_object %}
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
||||||
<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>
|
</a>
|
||||||
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
||||||
{% if request.path_info != user_profile_url %}
|
{% if request.path_info != user_profile_url %}
|
||||||
@ -59,7 +62,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if ".change_"|has_perm:club %}
|
{% if ".change_"|has_perm:club %}
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
||||||
data-turbolinks="false">
|
data-turbolinks="false">
|
||||||
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'member:club_detail' club.pk as club_detail_url %}
|
{% 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">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table managers %}
|
{% render_table managers %}
|
||||||
@ -23,7 +26,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
||||||
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table member_list %}
|
{% render_table member_list %}
|
||||||
@ -37,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
||||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||||
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
|
@ -47,7 +47,9 @@
|
|||||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||||
<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 }})
|
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
||||||
<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' %}
|
{% trans 'Change password' %}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@ -20,7 +22,9 @@
|
|||||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||||
<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 }})
|
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@ -39,20 +43,23 @@
|
|||||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
||||||
|
|
||||||
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
|
|
||||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
|
||||||
|
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% if user_object.pk == user.pk %}
|
{% if user_object.pk == user.pk %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||||
<i class="fa fa-cogs"></i>{% trans 'API token' %}
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -5,32 +5,98 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-info">
|
<div class="row mt-4">
|
||||||
<h4>À quoi sert un jeton d'authentification ?</h4>
|
<div class="col-xl-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h3>{% trans "Token authentication" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<h4>À quoi sert un jeton d'authentification ?</h4>
|
||||||
|
|
||||||
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a>.<br />
|
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a> via votre propre compte
|
||||||
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token <TOKEN></code>
|
depuis un client externe.<br />
|
||||||
pour pouvoir vous identifier.<br /><br />
|
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token <TOKEN></code>
|
||||||
|
pour pouvoir vous identifier.<br /><br />
|
||||||
|
|
||||||
Une documentation de l'API arrivera ultérieurement.
|
La documentation de l'API est disponible ici :
|
||||||
|
<a href="/doc/api/">{{ request.scheme }}://{{ request.get_host }}/doc/api/</a>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>{%trans 'Token' %} :</strong>
|
||||||
|
{% if 'show' in request.GET %}
|
||||||
|
{{ token.key }} (<a href="?">cacher</a>)
|
||||||
|
{% else %}
|
||||||
|
<em>caché</em> (<a href="?show">montrer</a>)
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
<strong>{%trans 'Created' %} :</strong> {{ token.created }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>{% trans "Warning" %} :</strong> regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton !
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<a href="?regenerate">
|
||||||
|
<button class="btn btn-primary">{% trans 'Regenerate token' %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h3>{% trans "OAuth2 authentication" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p>
|
||||||
|
La Note Kfet implémente également le protocole <a href="https://oauth.net/2/">OAuth2</a>, afin de
|
||||||
|
permettre à des applications tierces d'interagir avec la Note en récoltant des informations
|
||||||
|
(de connexion par exemple) voir en permettant des modifications à distance, par exemple lorsqu'il
|
||||||
|
s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Kfet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
L'usage de ce protocole est recommandé pour tout usage non personnel, car permet de mieux cibler
|
||||||
|
les droits dont on a besoin, en restreignant leur usage par jeton généré.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
La documentation vis-à-vis de l'usage de ce protocole est disponible ici :
|
||||||
|
<a href="/doc/external_services/oauth2/">{{ request.scheme }}://{{ request.get_host }}/doc/external_services/oauth2/</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Liste des URL à communiquer à votre application :
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
{% trans "Authorization:" %}
|
||||||
|
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{% trans "Token:" %}
|
||||||
|
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:token' %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{% trans "Revoke Token:" %}
|
||||||
|
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:revoke-token' %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{% trans "Introspect Token:" %}
|
||||||
|
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:introspect' %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<a class="btn btn-primary" href="{% url 'oauth2_provider:list' %}">{% trans "Show my applications" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<strong>{%trans 'Token' %} :</strong>
|
|
||||||
{% if 'show' in request.GET %}
|
|
||||||
{{ token.key }} (<a href="?">cacher</a>)
|
|
||||||
{% else %}
|
|
||||||
<em>caché</em> (<a href="?show">montrer</a>)
|
|
||||||
{% endif %}
|
|
||||||
<br />
|
|
||||||
<strong>{%trans 'Created' %} :</strong> {{ token.created }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<strong>Attention :</strong> regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton !
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href="?regenerate">
|
|
||||||
<button class="btn btn-primary">{% trans 'Regenerate token' %}</button>
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -18,7 +18,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="font-weight-bold">
|
||||||
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% render_table club_list %}
|
{% 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"
|
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||||
{% if "note.view_note"|has_perm:user_object.note %}
|
{% if "note.view_note"|has_perm:user_object.note %}
|
||||||
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
||||||
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="history_list">
|
<div id="history_list">
|
||||||
|
@ -7,7 +7,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% if can_manage_registrations %}
|
{% if can_manage_registrations %}
|
||||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||||
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
@ -21,7 +21,7 @@ from rest_framework.authtoken.models import Token
|
|||||||
from note.models import Alias, NoteUser
|
from note.models import Alias, NoteUser
|
||||||
from note.models.transactions import Transaction, SpecialTransaction
|
from note.models.transactions import Transaction, SpecialTransaction
|
||||||
from note.tables import HistoryTable, AliasTable
|
from note.tables import HistoryTable, AliasTable
|
||||||
from note_kfet.middlewares import _set_current_user_and_ip
|
from note_kfet.middlewares import _set_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||||
@ -41,7 +41,8 @@ class CustomLoginView(LoginView):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
self.request.user = form.get_user()
|
||||||
|
_set_current_request(self.request)
|
||||||
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
|
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
form.fields['email'].required = True
|
form.fields['email'].required = True
|
||||||
form.fields['email'].help_text = _("This address must be valid.")
|
form.fields['email'].help_text = _("This address must be valid.")
|
||||||
|
|
||||||
if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile):
|
if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile):
|
||||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||||
data=self.request.POST if self.request.POST else None)
|
data=self.request.POST if self.request.POST else None)
|
||||||
if not self.object.profile.report_frequency:
|
if not self.object.profile.report_frequency:
|
||||||
@ -153,13 +154,13 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
history_list = \
|
history_list = \
|
||||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\
|
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\
|
||||||
.order_by("-created_at")\
|
.order_by("-created_at")\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))
|
||||||
history_table = HistoryTable(history_list, prefix='transaction-')
|
history_table = HistoryTable(history_list, prefix='transaction-')
|
||||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||||
context['history_list'] = history_table
|
context['history_list'] = history_table
|
||||||
|
|
||||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\
|
||||||
.order_by("club__name", "-date_start")
|
.order_by("club__name", "-date_start")
|
||||||
# Display only the most recent membership
|
# Display only the most recent membership
|
||||||
club_list = club_list.distinct("club__name")\
|
club_list = club_list.distinct("club__name")\
|
||||||
@ -176,21 +177,20 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
modified_note.is_active = True
|
modified_note.is_active = True
|
||||||
modified_note.inactivity_reason = 'manual'
|
modified_note.inactivity_reason = 'manual'
|
||||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request.user, "note.change_noteuser_is_active",
|
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||||
modified_note)
|
|
||||||
old_note = NoteUser.objects.select_for_update().get(pk=user.note.pk)
|
old_note = NoteUser.objects.select_for_update().get(pk=user.note.pk)
|
||||||
modified_note.inactivity_reason = 'forced'
|
modified_note.inactivity_reason = 'forced'
|
||||||
modified_note._force_save = True
|
modified_note._force_save = True
|
||||||
modified_note.save()
|
modified_note.save()
|
||||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
.check_perm(self.request, "note.change_note_is_active", modified_note)
|
||||||
old_note._force_save = True
|
old_note._force_save = True
|
||||||
old_note._no_signal = True
|
old_note._no_signal = True
|
||||||
old_note.save()
|
old_note.save()
|
||||||
modified_note.refresh_from_db()
|
modified_note.refresh_from_db()
|
||||||
modified_note.is_active = True
|
modified_note.is_active = True
|
||||||
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
|
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
.check_perm(self.request, "note.change_note_is_active", modified_note)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\
|
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request, User, "view"))\
|
||||||
.filter(profile__registration_valid=False)
|
.filter(profile__registration_valid=False)
|
||||||
context["can_manage_registrations"] = pre_registered_users.exists()
|
context["can_manage_registrations"] = pre_registered_users.exists()
|
||||||
return context
|
return context
|
||||||
@ -256,8 +256,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(
|
context["aliases"] = AliasTable(
|
||||||
note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
|
||||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||||
note=context["object"].note,
|
note=context["object"].note,
|
||||||
name="",
|
name="",
|
||||||
normalized_name="",
|
normalized_name="",
|
||||||
@ -382,7 +382,7 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["can_add_club"] = PermissionBackend.check_perm(self.request.user, "member.add_club", Club(
|
context["can_add_club"] = PermissionBackend.check_perm(self.request, "member.add_club", Club(
|
||||||
name="",
|
name="",
|
||||||
email="club@example.com",
|
email="club@example.com",
|
||||||
))
|
))
|
||||||
@ -404,7 +404,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
club = context["club"]
|
club = context["club"]
|
||||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||||
club.update_membership_dates()
|
club.update_membership_dates()
|
||||||
# managers list
|
# managers list
|
||||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||||
@ -413,7 +413,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
||||||
# transaction history
|
# transaction history
|
||||||
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\
|
||||||
.order_by('-created_at')
|
.order_by('-created_at')
|
||||||
history_table = HistoryTable(club_transactions, prefix="history-")
|
history_table = HistoryTable(club_transactions, prefix="history-")
|
||||||
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
||||||
@ -422,7 +422,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
club_member = Membership.objects.filter(
|
club_member = Membership.objects.filter(
|
||||||
club=club,
|
club=club,
|
||||||
date_end__gte=date.today() - timedelta(days=15),
|
date_end__gte=date.today() - timedelta(days=15),
|
||||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
).filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\
|
||||||
.order_by("user__username", "-date_start")
|
.order_by("user__username", "-date_start")
|
||||||
# Display only the most recent membership
|
# Display only the most recent membership
|
||||||
club_member = club_member.distinct("user__username")\
|
club_member = club_member.distinct("user__username")\
|
||||||
@ -459,8 +459,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(note.alias.filter(
|
context["aliases"] = AliasTable(note.alias.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
|
||||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||||
note=context["object"].note,
|
note=context["object"].note,
|
||||||
name="",
|
name="",
|
||||||
normalized_name="",
|
normalized_name="",
|
||||||
@ -535,7 +535,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
form = context['form']
|
form = context['form']
|
||||||
|
|
||||||
if "club_pk" in self.kwargs: # We create a new membership.
|
if "club_pk" in self.kwargs: # We create a new membership.
|
||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view"))\
|
||||||
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
||||||
form.fields['credit_amount'].initial = club.membership_fee_paid
|
form.fields['credit_amount'].initial = club.membership_fee_paid
|
||||||
# Ensure that the user is member of the parent club and all its the family tree.
|
# Ensure that the user is member of the parent club and all its the family tree.
|
||||||
@ -655,8 +655,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
||||||
user=form.instance.user,
|
user=form.instance.user,
|
||||||
club=club.parent_club,
|
club=club.parent_club,
|
||||||
date_start__lte=timezone.now(),
|
date_start__gte=club.parent_club.membership_start,
|
||||||
date_end__gte=club.parent_club.membership_end,
|
|
||||||
).exists():
|
).exists():
|
||||||
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
||||||
error = True
|
error = True
|
||||||
@ -684,7 +683,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
"""
|
"""
|
||||||
# Get the club that is concerned by the membership
|
# Get the club that is concerned by the membership
|
||||||
if "club_pk" in self.kwargs: # get from url of new membership
|
if "club_pk" in self.kwargs: # get from url of new membership
|
||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view")) \
|
||||||
.get(pk=self.kwargs["club_pk"])
|
.get(pk=self.kwargs["club_pk"])
|
||||||
user = form.instance.user
|
user = form.instance.user
|
||||||
old_membership = None
|
old_membership = None
|
||||||
@ -868,7 +867,7 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
club = Club.objects.filter(
|
club = Club.objects.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Club, "view")
|
PermissionBackend.filter_queryset(self.request, Club, "view")
|
||||||
).get(pk=self.kwargs["pk"])
|
).get(pk=self.kwargs["pk"])
|
||||||
context["club"] = club
|
context["club"] = club
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'note.apps.NoteConfig'
|
default_app_config = 'note.apps.NoteConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -8,7 +8,7 @@ from rest_framework.exceptions import ValidationError
|
|||||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||||
from member.api.serializers import MembershipSerializer
|
from member.api.serializers import MembershipSerializer
|
||||||
from member.models import Membership
|
from member.models import Membership
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
# If the user has no right to see the note, then we only display the note identifier
|
# If the user has no right to see the note, then we only display the note identifier
|
||||||
return NotePolymorphicSerializer().to_representation(obj.note)\
|
return NotePolymorphicSerializer().to_representation(obj.note)\
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note)\
|
if PermissionBackend.check_perm(get_current_request(), "note.view_note", obj.note)\
|
||||||
else dict(
|
else dict(
|
||||||
id=obj.note.id,
|
id=obj.note.id,
|
||||||
name=str(obj.note),
|
name=str(obj.note),
|
||||||
@ -142,7 +142,7 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
|||||||
def get_membership(self, obj):
|
def get_membership(self, obj):
|
||||||
if isinstance(obj.note, NoteUser):
|
if isinstance(obj.note, NoteUser):
|
||||||
memberships = Membership.objects.filter(
|
memberships = Membership.objects.filter(
|
||||||
PermissionBackend.filter_queryset(get_current_authenticated_user(), Membership, "view")).filter(
|
PermissionBackend.filter_queryset(get_current_request(), Membership, "view")).filter(
|
||||||
user=obj.note.user,
|
user=obj.note.user,
|
||||||
club=2, # Kfet
|
club=2, # Kfet
|
||||||
).order_by("-date_start")
|
).order_by("-date_start")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -10,7 +11,6 @@ from rest_framework import viewsets
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
from note_kfet.middlewares import get_current_session
|
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||||
@ -40,12 +40,11 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
|||||||
Parse query and apply filters.
|
Parse query and apply filters.
|
||||||
:return: The filtered set of requested notes
|
:return: The filtered set of requested notes
|
||||||
"""
|
"""
|
||||||
user = self.request.user
|
queryset = self.queryset.filter(PermissionBackend.filter_queryset(self.request, Note, "view")
|
||||||
get_current_session().setdefault("permission_mask", 42)
|
| PermissionBackend.filter_queryset(self.request, NoteUser, "view")
|
||||||
queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view")
|
| PermissionBackend.filter_queryset(self.request, NoteClub, "view")
|
||||||
| PermissionBackend.filter_queryset(user, NoteUser, "view")
|
| PermissionBackend.filter_queryset(self.request, NoteSpecial, "view"))\
|
||||||
| PermissionBackend.filter_queryset(user, NoteClub, "view")
|
.distinct()
|
||||||
| PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct()
|
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", ".*")
|
alias = self.request.query_params.get("alias", ".*")
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
@ -67,7 +66,8 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
serializer_class = AliasSerializer
|
serializer_class = AliasSerializer
|
||||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||||
|
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||||
ordering_fields = ['name', 'normalized_name', ]
|
ordering_fields = ['name', 'normalized_name', ]
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
@ -118,7 +118,8 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
serializer_class = ConsumerSerializer
|
serializer_class = ConsumerSerializer
|
||||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||||
|
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||||
ordering_fields = ['name', 'normalized_name', ]
|
ordering_fields = ['name', 'normalized_name', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -133,23 +134,31 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", None)
|
alias = self.request.query_params.get("alias", None)
|
||||||
|
# Check if this is a valid regex. If not, we won't check regex
|
||||||
|
try:
|
||||||
|
re.compile(alias)
|
||||||
|
valid_regex = True
|
||||||
|
except (re.error, TypeError):
|
||||||
|
valid_regex = False
|
||||||
|
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||||
|
alias_prefix = '^' if valid_regex else ''
|
||||||
queryset = queryset.prefetch_related('note')
|
queryset = queryset.prefetch_related('note')
|
||||||
|
|
||||||
if alias:
|
if alias:
|
||||||
# We match first an alias if it is matched without normalization,
|
# We match first an alias if it is matched without normalization,
|
||||||
# then if the normalized pattern matches a normalized alias.
|
# then if the normalized pattern matches a normalized alias.
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
name__iregex="^" + alias
|
**{f'name{suffix}': alias_prefix + alias}
|
||||||
).union(
|
).union(
|
||||||
queryset.filter(
|
queryset.filter(
|
||||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
||||||
& ~Q(name__iregex="^" + alias)
|
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
||||||
),
|
),
|
||||||
all=True).union(
|
all=True).union(
|
||||||
queryset.filter(
|
queryset.filter(
|
||||||
Q(normalized_name__iregex="^" + alias.lower())
|
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
|
||||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
||||||
& ~Q(name__iregex="^" + alias)
|
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
||||||
),
|
),
|
||||||
all=True)
|
all=True)
|
||||||
|
|
||||||
@ -205,7 +214,5 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
|||||||
ordering_fields = ['created_at', 'amount', ]
|
ordering_fields = ['created_at', 'amount', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
return self.model.objects.filter(PermissionBackend.filter_queryset(self.request, self.model, "view"))\
|
||||||
get_current_session().setdefault("permission_mask", 42)
|
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))\
|
|
||||||
.order_by("created_at", "id")
|
.order_by("created_at", "id")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.global_settings import DEFAULT_FROM_EMAIL
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
@ -190,8 +189,8 @@ class NoteClub(Note):
|
|||||||
def send_mail_negative_balance(self):
|
def send_mail_negative_balance(self):
|
||||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||||
send_mail("[Note Kfet] Passage en négatif (club {})".format(self.club.name), plain_text, DEFAULT_FROM_EMAIL,
|
send_mail("[Note Kfet] Passage en négatif (club {})".format(self.club.name), plain_text,
|
||||||
[self.club.email], html_message=html)
|
settings.DEFAULT_FROM_EMAIL, [self.club.email], html_message=html)
|
||||||
|
|
||||||
|
|
||||||
class NoteSpecial(Note):
|
class NoteSpecial(Note):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
// Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// When a transaction is performed, lock the interface to prevent spam clicks.
|
// When a transaction is performed, lock the interface to prevent spam clicks.
|
||||||
|
@ -222,6 +222,13 @@ $(document).ready(function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Make transfer when pressing Enter on the amount section
|
||||||
|
$('#amount, #reason, #last_name, #first_name, #bank').keypress((event) => {
|
||||||
|
if (event.originalEvent.charCode === 13) {
|
||||||
|
$('#btn_transfer').click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$('#btn_transfer').click(function () {
|
$('#btn_transfer').click(function () {
|
||||||
if (LOCK) { return }
|
if (LOCK) { return }
|
||||||
|
|
||||||
@ -243,7 +250,7 @@ $('#btn_transfer').click(function () {
|
|||||||
error = true
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = Math.floor(100 * amount_field.val())
|
const amount = Math.round(100 * amount_field.val())
|
||||||
if (amount > 2147483647) {
|
if (amount > 2147483647) {
|
||||||
amount_field.addClass('is-invalid')
|
amount_field.addClass('is-invalid')
|
||||||
$('#amount-required').html('<strong>' + gettext('The amount must stay under 21,474,836.47 €.') + '</strong>')
|
$('#amount-required').html('<strong>' + gettext('The amount must stay under 21,474,836.47 €.') + '</strong>')
|
||||||
@ -348,14 +355,14 @@ $('#btn_transfer').click(function () {
|
|||||||
destination_alias: dest.name
|
destination_alias: dest.name
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, gettext('insufficient funds')]), 'danger', 10000)
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, gettext('insufficient funds')]), 'danger', 10000)
|
||||||
reset()
|
reset()
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
const errObj = JSON.parse(err.responseText)
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error) { error = err.responseText }
|
if (!error) { error = err.responseText }
|
||||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, error]), 'danger')
|
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, error]), 'danger')
|
||||||
LOCK = false
|
LOCK = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import html
|
import html
|
||||||
@ -7,7 +7,7 @@ import django_tables2 as tables
|
|||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .models.notes import Alias
|
from .models.notes import Alias
|
||||||
@ -88,16 +88,16 @@ class HistoryTable(tables.Table):
|
|||||||
"class": lambda record:
|
"class": lambda record:
|
||||||
str(record.valid).lower()
|
str(record.valid).lower()
|
||||||
+ (' validate' if record.source.is_active and record.destination.is_active and PermissionBackend
|
+ (' validate' if record.source.is_active and record.destination.is_active and PermissionBackend
|
||||||
.check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record)
|
.check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record)
|
||||||
else ''),
|
else ''),
|
||||||
"data-toggle": "tooltip",
|
"data-toggle": "tooltip",
|
||||||
"title": lambda record: (_("Click to invalidate") if record.valid else _("Click to validate"))
|
"title": lambda record: (_("Click to invalidate") if record.valid else _("Click to validate"))
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
if PermissionBackend.check_perm(get_current_request(),
|
||||||
"note.change_transaction_invalidity_reason", record)
|
"note.change_transaction_invalidity_reason", record)
|
||||||
and record.source.is_active and record.destination.is_active else None,
|
and record.source.is_active and record.destination.is_active else None,
|
||||||
"onclick": lambda record: 'de_validate(' + str(record.id) + ', ' + str(record.valid).lower()
|
"onclick": lambda record: 'de_validate(' + str(record.id) + ', ' + str(record.valid).lower()
|
||||||
+ ', "' + str(record.__class__.__name__) + '")'
|
+ ', "' + str(record.__class__.__name__) + '")'
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
if PermissionBackend.check_perm(get_current_request(),
|
||||||
"note.change_transaction_invalidity_reason", record)
|
"note.change_transaction_invalidity_reason", record)
|
||||||
and record.source.is_active and record.destination.is_active else None,
|
and record.source.is_active and record.destination.is_active else None,
|
||||||
"onmouseover": lambda record: '$("#invalidity_reason_'
|
"onmouseover": lambda record: '$("#invalidity_reason_'
|
||||||
@ -126,7 +126,7 @@ class HistoryTable(tables.Table):
|
|||||||
When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason
|
When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason
|
||||||
"""
|
"""
|
||||||
has_perm = PermissionBackend \
|
has_perm = PermissionBackend \
|
||||||
.check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record)
|
.check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record)
|
||||||
|
|
||||||
val = "✔" if value else "✖"
|
val = "✔" if value else "✖"
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ class AliasTable(tables.Table):
|
|||||||
extra_context={"delete_trans": _('delete')},
|
extra_context={"delete_trans": _('delete')},
|
||||||
attrs={'td': {'class': lambda record: 'col-sm-1' + (
|
attrs={'td': {'class': lambda record: 'col-sm-1' + (
|
||||||
' d-none' if not PermissionBackend.check_perm(
|
' d-none' if not PermissionBackend.check_perm(
|
||||||
get_current_authenticated_user(), "note.delete_alias",
|
get_current_request(), "note.delete_alias",
|
||||||
record) else '')}}, verbose_name=_("Delete"), )
|
record) else '')}}, verbose_name=_("Delete"), )
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +129,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{# Mode switch #}
|
{# Mode switch #}
|
||||||
<div class="card-footer border-primary">
|
<div class="card-footer border-primary">
|
||||||
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
||||||
<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>
|
</a>
|
||||||
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
||||||
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
||||||
|
@ -10,21 +10,25 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
{# bandeau transfert/crédit/débit/activité #}
|
{# bandeau transfert/crédit/débit/activité #}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-12">
|
<div class="col-xl-12">
|
||||||
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons">
|
<div class="btn-group btn-block">
|
||||||
<label for="type_transfer" class="btn btn-sm btn-outline-primary active">
|
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons">
|
||||||
<input type="radio" name="transaction_type" id="type_transfer">
|
<label for="type_transfer" class="btn btn-sm btn-outline-primary active">
|
||||||
{% trans "Transfer" %}
|
<input type="radio" name="transaction_type" id="type_transfer">
|
||||||
</label>
|
{% trans "Transfer" %}
|
||||||
{% if "note.notespecial"|not_empty_model_list %}
|
|
||||||
<label for="type_credit" class="btn btn-sm btn-outline-primary">
|
|
||||||
<input type="radio" name="transaction_type" id="type_credit">
|
|
||||||
{% trans "Credit" %}
|
|
||||||
</label>
|
</label>
|
||||||
<label for="type_debit" class="btn btn-sm btn-outline-primary">
|
{% if "note.notespecial"|not_empty_model_list %}
|
||||||
<input type="radio" name="transaction_type" id="type_debit">
|
<label for="type_credit" class="btn btn-sm btn-outline-primary">
|
||||||
{% trans "Debit" %}
|
<input type="radio" name="transaction_type" id="type_credit">
|
||||||
</label>
|
{% trans "Credit" %}
|
||||||
{% endif %}
|
</label>
|
||||||
|
<label for="type_debit" class="btn btn-sm btn-outline-primary">
|
||||||
|
<input type="radio" name="transaction_type" id="type_debit">
|
||||||
|
{% trans "Debit" %}
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Add shortcuts for opened activites if necessary #}
|
||||||
{% for activity in activities_open %}
|
{% for activity in activities_open %}
|
||||||
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">
|
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">
|
||||||
{% trans "Entries" %} {{ activity.name }}
|
{% trans "Entries" %} {{ activity.name }}
|
||||||
@ -57,7 +61,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<ul class="list-group list-group-flush" id="source_note_list">
|
<ul class="list-group list-group-flush" id="source_note_list">
|
||||||
</ul>
|
</ul>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<select id="credit_type" class="custom-select d-none">
|
<select id="credit_type" class="form-control custom-select d-none">
|
||||||
{% for special_type in special_types %}
|
{% for special_type in special_types %}
|
||||||
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
|
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -84,7 +88,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<ul class="list-group list-group-flush" id="dest_note_list">
|
<ul class="list-group list-group-flush" id="dest_note_list">
|
||||||
</ul>
|
</ul>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<select id="debit_type" class="custom-select d-none">
|
<select id="debit_type" class="form-control custom-select d-none">
|
||||||
{% for special_type in special_types %}
|
{% for special_type in special_types %}
|
||||||
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
|
<option value="{{ special_type.id }}">{{ special_type.special_type }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.tests import TestAPI
|
from api.tests import TestAPI
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@ -38,7 +38,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
|
|||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
# retrieves only Transaction that user has the right to see.
|
# retrieves only Transaction that user has the right to see.
|
||||||
return Transaction.objects.filter(
|
return Transaction.objects.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
PermissionBackend.filter_queryset(self.request, Transaction, "view")
|
||||||
).order_by("-created_at").all()[:20]
|
).order_by("-created_at").all()[:20]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -47,16 +47,16 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
|
|||||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
||||||
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
|
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
|
||||||
context['special_types'] = NoteSpecial.objects\
|
context['special_types'] = NoteSpecial.objects\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, NoteSpecial, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request, NoteSpecial, "view"))\
|
||||||
.order_by("special_type").all()
|
.order_by("special_type").all()
|
||||||
|
|
||||||
# Add a shortcut for entry page for open activities
|
# Add a shortcut for entry page for open activities
|
||||||
if "activity" in settings.INSTALLED_APPS:
|
if "activity" in settings.INSTALLED_APPS:
|
||||||
from activity.models import Activity
|
from activity.models import Activity
|
||||||
activities_open = Activity.objects.filter(open=True).filter(
|
activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
|
||||||
context["activities_open"] = [a for a in activities_open
|
context["activities_open"] = [a for a in activities_open
|
||||||
if PermissionBackend.check_perm(self.request.user,
|
if PermissionBackend.check_perm(self.request,
|
||||||
"activity.add_entry",
|
"activity.add_entry",
|
||||||
Entry(activity=a,
|
Entry(activity=a,
|
||||||
note=self.request.user.note, ))]
|
note=self.request.user.note, ))]
|
||||||
@ -159,7 +159,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
templates = TransactionTemplate.objects.filter(
|
templates = TransactionTemplate.objects.filter(
|
||||||
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view")
|
||||||
)
|
)
|
||||||
if not templates.exists():
|
if not templates.exists():
|
||||||
raise PermissionDenied(_("You can't see any button."))
|
raise PermissionDenied(_("You can't see any button."))
|
||||||
@ -170,7 +170,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
restrict to the transaction history the user can see.
|
restrict to the transaction history the user can see.
|
||||||
"""
|
"""
|
||||||
return Transaction.objects.filter(
|
return Transaction.objects.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
PermissionBackend.filter_queryset(self.request, Transaction, "view")
|
||||||
).order_by("-created_at").all()[:20]
|
).order_by("-created_at").all()[:20]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -180,13 +180,13 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|||||||
# for each category, find which transaction templates the user can see.
|
# for each category, find which transaction templates the user can see.
|
||||||
for category in categories:
|
for category in categories:
|
||||||
category.templates_filtered = category.templates.filter(
|
category.templates_filtered = category.templates.filter(
|
||||||
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view")
|
||||||
).filter(display=True).order_by('name').all()
|
).filter(display=True).order_by('name').all()
|
||||||
|
|
||||||
context['categories'] = [cat for cat in categories if cat.templates_filtered]
|
context['categories'] = [cat for cat in categories if cat.templates_filtered]
|
||||||
# some transactiontemplate are put forward to find them easily
|
# some transactiontemplate are put forward to find them easily
|
||||||
context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
|
context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
|
||||||
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view")
|
||||||
).order_by('name').all()
|
).order_by('name').all()
|
||||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView
|
|||||||
data = form.cleaned_data if form.is_valid() else {}
|
data = form.cleaned_data if form.is_valid() else {}
|
||||||
|
|
||||||
transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter(
|
transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
PermissionBackend.filter_queryset(self.request, Transaction, "view"))\
|
||||||
.filter(Q(source=self.object) | Q(destination=self.object)).order_by('-created_at')
|
.filter(Q(source=self.object) | Q(destination=self.object)).order_by('-created_at')
|
||||||
|
|
||||||
if "source" in data and data["source"]:
|
if "source" in data and data["source"]:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
default_app_config = 'permission.apps.PermissionConfig'
|
default_app_config = 'permission.apps.PermissionConfig'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import PermissionViewSet, RoleViewSet
|
from .views import PermissionViewSet, RoleViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.models import User, AnonymousUser
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q, F
|
from django.db.models import Q, F
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from note.models import Note, NoteUser, NoteClub, NoteSpecial
|
from note.models import Note, NoteUser, NoteClub, NoteSpecial
|
||||||
from note_kfet.middlewares import get_current_session
|
from note_kfet.middlewares import get_current_request
|
||||||
from member.models import Membership, Club
|
from member.models import Membership, Club
|
||||||
|
|
||||||
from .decorators import memoize
|
from .decorators import memoize
|
||||||
@ -26,14 +26,31 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@memoize
|
@memoize
|
||||||
def get_raw_permissions(user, t):
|
def get_raw_permissions(request, t):
|
||||||
"""
|
"""
|
||||||
Query permissions of a certain type for a user, then memoize it.
|
Query permissions of a certain type for a user, then memoize it.
|
||||||
:param user: The owner of the permissions
|
:param request: The current request
|
||||||
:param t: The type of the permissions: view, change, add or delete
|
:param t: The type of the permissions: view, change, add or delete
|
||||||
:return: The queryset of the permissions of the user (memoized) grouped by clubs
|
:return: The queryset of the permissions of the user (memoized) grouped by clubs
|
||||||
"""
|
"""
|
||||||
if isinstance(user, AnonymousUser):
|
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||||
|
# OAuth2 Authentication
|
||||||
|
user = request.auth.user
|
||||||
|
|
||||||
|
def permission_filter(membership_obj):
|
||||||
|
query = Q(pk=-1)
|
||||||
|
for scope in request.auth.scope.split(' '):
|
||||||
|
permission_id, club_id = scope.split('_')
|
||||||
|
if int(club_id) == membership_obj.club_id:
|
||||||
|
query |= Q(pk=permission_id)
|
||||||
|
return query
|
||||||
|
else:
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
def permission_filter(membership_obj):
|
||||||
|
return Q(mask__rank__lte=request.session.get("permission_mask", 42))
|
||||||
|
|
||||||
|
if user.is_anonymous:
|
||||||
# Unauthenticated users have no permissions
|
# Unauthenticated users have no permissions
|
||||||
return Permission.objects.none()
|
return Permission.objects.none()
|
||||||
|
|
||||||
@ -43,7 +60,7 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
for membership in memberships:
|
for membership in memberships:
|
||||||
for role in membership.roles.all():
|
for role in membership.roles.all():
|
||||||
for perm in role.permissions.filter(type=t, mask__rank__lte=get_current_session().get("permission_mask", -1)).all():
|
for perm in role.permissions.filter(permission_filter(membership), type=t).all():
|
||||||
if not perm.permanent:
|
if not perm.permanent:
|
||||||
if membership.date_start > date.today() or membership.date_end < date.today():
|
if membership.date_start > date.today() or membership.date_end < date.today():
|
||||||
continue
|
continue
|
||||||
@ -52,16 +69,22 @@ class PermissionBackend(ModelBackend):
|
|||||||
return perms
|
return perms
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def permissions(user, model, type):
|
def permissions(request, model, type):
|
||||||
"""
|
"""
|
||||||
List all permissions of the given user that applies to a given model and a give type
|
List all permissions of the given user that applies to a given model and a give type
|
||||||
:param user: The owner of the permissions
|
:param request: The current request
|
||||||
:param model: The model that the permissions shoud apply
|
:param model: The model that the permissions shoud apply
|
||||||
:param type: The type of the permissions: view, change, add or delete
|
:param type: The type of the permissions: view, change, add or delete
|
||||||
:return: A generator of the requested permissions
|
:return: A generator of the requested permissions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for permission in PermissionBackend.get_raw_permissions(user, type):
|
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||||
|
# OAuth2 Authentication
|
||||||
|
user = request.auth.user
|
||||||
|
else:
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
for permission in PermissionBackend.get_raw_permissions(request, type):
|
||||||
if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.membership:
|
if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.membership:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -88,20 +111,26 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@memoize
|
@memoize
|
||||||
def filter_queryset(user, model, t, field=None):
|
def filter_queryset(request, model, t, field=None):
|
||||||
"""
|
"""
|
||||||
Filter a queryset by considering the permissions of a given user.
|
Filter a queryset by considering the permissions of a given user.
|
||||||
:param user: The owner of the permissions that are fetched
|
:param request: The current request
|
||||||
:param model: The concerned model of the queryset
|
:param model: The concerned model of the queryset
|
||||||
:param t: The type of modification (view, add, change, delete)
|
:param t: The type of modification (view, add, change, delete)
|
||||||
:param field: The field of the model to test, if concerned
|
:param field: The field of the model to test, if concerned
|
||||||
:return: A query that corresponds to the filter to give to a queryset
|
:return: A query that corresponds to the filter to give to a queryset
|
||||||
"""
|
"""
|
||||||
if user is None or isinstance(user, AnonymousUser):
|
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||||
|
# OAuth2 Authentication
|
||||||
|
user = request.auth.user
|
||||||
|
else:
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
if user is None or user.is_anonymous:
|
||||||
# Anonymous users can't do anything
|
# Anonymous users can't do anything
|
||||||
return Q(pk=-1)
|
return Q(pk=-1)
|
||||||
|
|
||||||
if user.is_superuser and get_current_session().get("permission_mask", -1) >= 42:
|
if user.is_superuser and request.session.get("permission_mask", -1) >= 42:
|
||||||
# Superusers have all rights
|
# Superusers have all rights
|
||||||
return Q()
|
return Q()
|
||||||
|
|
||||||
@ -110,7 +139,7 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
# Never satisfied
|
# Never satisfied
|
||||||
query = Q(pk=-1)
|
query = Q(pk=-1)
|
||||||
perms = PermissionBackend.permissions(user, model, t)
|
perms = PermissionBackend.permissions(request, model, t)
|
||||||
for perm in perms:
|
for perm in perms:
|
||||||
if perm.field and field != perm.field:
|
if perm.field and field != perm.field:
|
||||||
continue
|
continue
|
||||||
@ -122,7 +151,7 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@memoize
|
@memoize
|
||||||
def check_perm(user_obj, perm, obj=None):
|
def check_perm(request, perm, obj=None):
|
||||||
"""
|
"""
|
||||||
Check is the given user has the permission over a given object.
|
Check is the given user has the permission over a given object.
|
||||||
The result is then memoized.
|
The result is then memoized.
|
||||||
@ -130,10 +159,19 @@ class PermissionBackend(ModelBackend):
|
|||||||
primary key, the result is not memoized. Moreover, the right could change
|
primary key, the result is not memoized. Moreover, the right could change
|
||||||
(e.g. for a transaction, the balance of the user could change)
|
(e.g. for a transaction, the balance of the user could change)
|
||||||
"""
|
"""
|
||||||
if user_obj is None or isinstance(user_obj, AnonymousUser):
|
# Requested by a shell
|
||||||
|
if request is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sess = get_current_session()
|
user_obj = request.user
|
||||||
|
sess = request.session
|
||||||
|
|
||||||
|
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||||
|
# OAuth2 Authentication
|
||||||
|
user_obj = request.auth.user
|
||||||
|
|
||||||
|
if user_obj is None or user_obj.is_anonymous:
|
||||||
|
return False
|
||||||
|
|
||||||
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
||||||
return True
|
return True
|
||||||
@ -147,16 +185,19 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
ct = ContentType.objects.get_for_model(obj)
|
ct = ContentType.objects.get_for_model(obj)
|
||||||
if any(permission.applies(obj, perm_type, perm_field)
|
if any(permission.applies(obj, perm_type, perm_field)
|
||||||
for permission in PermissionBackend.permissions(user_obj, ct, perm_type)):
|
for permission in PermissionBackend.permissions(request, ct, perm_type)):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_perm(self, user_obj, perm, obj=None):
|
def has_perm(self, user_obj, perm, obj=None):
|
||||||
return PermissionBackend.check_perm(user_obj, perm, obj)
|
# Warning: this does not check that user_obj has the permission,
|
||||||
|
# but if the current request has the permission.
|
||||||
|
# This function is implemented for backward compatibility, and should not be used.
|
||||||
|
return PermissionBackend.check_perm(get_current_request(), perm, obj)
|
||||||
|
|
||||||
def has_module_perms(self, user_obj, app_label):
|
def has_module_perms(self, user_obj, app_label):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_all_permissions(self, user_obj, obj=None):
|
def get_all_permissions(self, user_obj, obj=None):
|
||||||
ct = ContentType.objects.get_for_model(obj)
|
ct = ContentType.objects.get_for_model(obj)
|
||||||
return list(self.permissions(user_obj, ct, "view"))
|
return list(self.permissions(get_current_request(), ct, "view"))
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
import sys
|
import sys
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
from note_kfet.middlewares import get_current_session
|
from note_kfet.middlewares import get_current_request
|
||||||
|
|
||||||
|
|
||||||
def memoize(f):
|
def memoize(f):
|
||||||
@ -48,11 +48,11 @@ def memoize(f):
|
|||||||
last_collect = time()
|
last_collect = time()
|
||||||
|
|
||||||
# If there is no session, then we don't memoize anything.
|
# If there is no session, then we don't memoize anything.
|
||||||
sess = get_current_session()
|
request = get_current_request()
|
||||||
if sess is None or sess.session_key is None:
|
if request is None or request.session is None or request.session.session_key is None:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
sess_key = sess.session_key
|
sess_key = request.session.session_key
|
||||||
if sess_key not in sess_funs:
|
if sess_key not in sess_funs:
|
||||||
# lru_cache makes the job of memoization
|
# lru_cache makes the job of memoization
|
||||||
# We store only the 512 latest data per session. It has to be enough.
|
# We store only the 512 latest data per session. It has to be enough.
|
||||||
|
@ -111,12 +111,12 @@
|
|||||||
"note",
|
"note",
|
||||||
"alias"
|
"alias"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", [\"OR\", {\"note__noteuser__user__memberships__club__name\": \"Kfet\", \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__noteclub__isnull\": false}], {\"note__is_active\": true}]",
|
"query": "[\"AND\", [\"OR\", {\"note__noteuser__user__memberships__club__name\": \"BDE\", \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__noteclub__isnull\": false}], {\"note__is_active\": true}]",
|
||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": false,
|
||||||
"description": "Voir les aliases des notes des clubs et des adhérents du club Kfet"
|
"description": "Voir les aliases des notes des clubs et des adhérents du club BDE"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -627,7 +627,7 @@
|
|||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Voir les personnes qu'on a invitées"
|
"description": "Voir les personnes qu'on a invitées"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1235,7 +1235,7 @@
|
|||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Voir le dernier WEI"
|
"description": "Voir le dernier WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1267,7 +1267,7 @@
|
|||||||
"type": "add",
|
"type": "add",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "M'inscrire au dernier WEI"
|
"description": "M'inscrire au dernier WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1331,7 +1331,7 @@
|
|||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Voir ma propre inscription WEI"
|
"description": "Voir ma propre inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1379,7 +1379,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "soge_credit",
|
"field": "soge_credit",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Indiquer si mon inscription WEI est payée par la Société générale tant qu'elle n'est pas validée"
|
"description": "Indiquer si mon inscription WEI est payée par la Société générale tant qu'elle n'est pas validée"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1427,7 +1427,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "birth_date",
|
"field": "birth_date",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier la date de naissance de ma propre inscription WEI"
|
"description": "Modifier la date de naissance de ma propre inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1459,7 +1459,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "gender",
|
"field": "gender",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier le genre de ma propre inscription WEI"
|
"description": "Modifier le genre de ma propre inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1491,7 +1491,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "health_issues",
|
"field": "health_issues",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier mes problèmes de santé de mon inscription WEI"
|
"description": "Modifier mes problèmes de santé de mon inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1523,7 +1523,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "emergency_contact_name",
|
"field": "emergency_contact_name",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier le nom du contact en cas d'urgence de mon inscription WEI"
|
"description": "Modifier le nom du contact en cas d'urgence de mon inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1555,7 +1555,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "emergency_contact_phone",
|
"field": "emergency_contact_phone",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier le téléphone du contact en cas d'urgence de mon inscription WEI"
|
"description": "Modifier le téléphone du contact en cas d'urgence de mon inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1699,7 +1699,7 @@
|
|||||||
"type": "add",
|
"type": "add",
|
||||||
"mask": 3,
|
"mask": 3,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Créer une adhésion WEI pour le dernier WEI"
|
"description": "Créer une adhésion WEI pour le dernier WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2003,7 +2003,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "clothing_cut",
|
"field": "clothing_cut",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier ma coupe de vêtements de mon inscription WEI"
|
"description": "Modifier ma coupe de vêtements de mon inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2035,7 +2035,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "clothing_size",
|
"field": "clothing_size",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier la taille de vêtements de mon inscription WEI"
|
"description": "Modifier la taille de vêtements de mon inscription WEI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2243,7 +2243,7 @@
|
|||||||
"type": "change",
|
"type": "change",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "information_json",
|
"field": "information_json",
|
||||||
"permanent": false,
|
"permanent": true,
|
||||||
"description": "Modifier mes préférences en terme de bus et d'équipe si mon inscription n'est pas validée et que je suis en 2A+"
|
"description": "Modifier mes préférences en terme de bus et d'équipe si mon inscription n'est pas validée et que je suis en 2A+"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2883,6 +2883,7 @@
|
|||||||
3,
|
3,
|
||||||
4,
|
4,
|
||||||
5,
|
5,
|
||||||
|
6,
|
||||||
7,
|
7,
|
||||||
8,
|
8,
|
||||||
9,
|
9,
|
||||||
@ -2890,6 +2891,10 @@
|
|||||||
11,
|
11,
|
||||||
12,
|
12,
|
||||||
13,
|
13,
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
22,
|
22,
|
||||||
48,
|
48,
|
||||||
52,
|
52,
|
||||||
@ -2907,11 +2912,6 @@
|
|||||||
"for_club": 2,
|
"for_club": 2,
|
||||||
"name": "Adh\u00e9rent Kfet",
|
"name": "Adh\u00e9rent Kfet",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
6,
|
|
||||||
14,
|
|
||||||
15,
|
|
||||||
16,
|
|
||||||
17,
|
|
||||||
22,
|
22,
|
||||||
34,
|
34,
|
||||||
36,
|
36,
|
||||||
@ -3048,6 +3048,7 @@
|
|||||||
31,
|
31,
|
||||||
32,
|
32,
|
||||||
33,
|
33,
|
||||||
|
43,
|
||||||
51,
|
51,
|
||||||
53,
|
53,
|
||||||
54,
|
54,
|
||||||
@ -3304,6 +3305,7 @@
|
|||||||
30,
|
30,
|
||||||
31,
|
31,
|
||||||
70,
|
70,
|
||||||
|
72,
|
||||||
143,
|
143,
|
||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
@ -3495,7 +3497,7 @@
|
|||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 20,
|
"pk": 20,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": 2,
|
"for_club": 1,
|
||||||
"name": "PC Kfet",
|
"name": "PC Kfet",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
6,
|
6,
|
||||||
@ -3511,6 +3513,8 @@
|
|||||||
56,
|
56,
|
||||||
57,
|
57,
|
||||||
58,
|
58,
|
||||||
|
70,
|
||||||
|
72,
|
||||||
135,
|
135,
|
||||||
137,
|
137,
|
||||||
143,
|
143,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework.permissions import DjangoObjectPermissions
|
from rest_framework.permissions import DjangoObjectPermissions
|
||||||
@ -45,7 +45,7 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
|||||||
|
|
||||||
perms = self.get_required_object_permissions(request.method, model_cls)
|
perms = self.get_required_object_permissions(request.method, model_cls)
|
||||||
# if not user.has_perms(perms, obj):
|
# if not user.has_perms(perms, obj):
|
||||||
if not all(PermissionBackend.check_perm(user, perm, obj) for perm in perms):
|
if not all(PermissionBackend.check_perm(request, perm, obj) for perm in perms):
|
||||||
# If the user does not have permissions we need to determine if
|
# If the user does not have permissions we need to determine if
|
||||||
# they have read permissions to see 403, or not, and simply see
|
# they have read permissions to see 403, or not, and simply see
|
||||||
# a 404 response.
|
# a 404 response.
|
||||||
|
34
apps/permission/scopes.py
Normal file
34
apps/permission/scopes.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from oauth2_provider.scopes import BaseScopes
|
||||||
|
from member.models import Club
|
||||||
|
from note_kfet.middlewares import get_current_request
|
||||||
|
|
||||||
|
from .backends import PermissionBackend
|
||||||
|
from .models import Permission
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionScopes(BaseScopes):
|
||||||
|
"""
|
||||||
|
An OAuth2 scope is defined by a permission object and a club.
|
||||||
|
A token will have a subset of permissions from the owner of the application,
|
||||||
|
and can be useful to make queries through the API with limited privileges.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_all_scopes(self):
|
||||||
|
return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
|
||||||
|
for p in Permission.objects.all() for club in Club.objects.all()}
|
||||||
|
|
||||||
|
def get_available_scopes(self, application=None, request=None, *args, **kwargs):
|
||||||
|
if not application:
|
||||||
|
return []
|
||||||
|
return [f"{p.id}_{p.membership.club.id}"
|
||||||
|
for t in Permission.PERMISSION_TYPES
|
||||||
|
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
|
||||||
|
|
||||||
|
def get_default_scopes(self, application=None, request=None, *args, **kwargs):
|
||||||
|
if not application:
|
||||||
|
return []
|
||||||
|
return [f"{p.id}_{p.membership.club.id}"
|
||||||
|
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
|
@ -1,9 +1,9 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +16,9 @@ EXCLUDED = [
|
|||||||
'contenttypes.contenttype',
|
'contenttypes.contenttype',
|
||||||
'logs.changelog',
|
'logs.changelog',
|
||||||
'migrations.migration',
|
'migrations.migration',
|
||||||
|
'oauth2_provider.accesstoken',
|
||||||
|
'oauth2_provider.grant',
|
||||||
|
'oauth2_provider.refreshtoken',
|
||||||
'sessions.session',
|
'sessions.session',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -31,8 +34,8 @@ def pre_save_object(sender, instance, **kwargs):
|
|||||||
if hasattr(instance, "_force_save") or hasattr(instance, "_no_signal"):
|
if hasattr(instance, "_force_save") or hasattr(instance, "_no_signal"):
|
||||||
return
|
return
|
||||||
|
|
||||||
user = get_current_authenticated_user()
|
request = get_current_request()
|
||||||
if user is None:
|
if request is None:
|
||||||
# Action performed on shell is always granted
|
# Action performed on shell is always granted
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ def pre_save_object(sender, instance, **kwargs):
|
|||||||
# We check if the user can change the model
|
# We check if the user can change the model
|
||||||
|
|
||||||
# If the user has all right on a model, then OK
|
# If the user has all right on a model, then OK
|
||||||
if PermissionBackend.check_perm(user, app_label + ".change_" + model_name, instance):
|
if PermissionBackend.check_perm(request, app_label + ".change_" + model_name, instance):
|
||||||
return
|
return
|
||||||
|
|
||||||
# In the other case, we check if he/she has the right to change one field
|
# In the other case, we check if he/she has the right to change one field
|
||||||
@ -58,7 +61,14 @@ def pre_save_object(sender, instance, **kwargs):
|
|||||||
# If the field wasn't modified, no need to check the permissions
|
# If the field wasn't modified, no need to check the permissions
|
||||||
if old_value == new_value:
|
if old_value == new_value:
|
||||||
continue
|
continue
|
||||||
if not PermissionBackend.check_perm(user, app_label + ".change_" + model_name + "_" + field_name, instance):
|
|
||||||
|
if app_label == 'auth' and model_name == 'user' and field.name == 'password' and request.user.is_anonymous:
|
||||||
|
# We must ignore password changes from anonymous users since it can be done by people that forgot
|
||||||
|
# their password. We trust password change form.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not PermissionBackend.check_perm(request, app_label + ".change_" + model_name + "_" + field_name,
|
||||||
|
instance):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
_("You don't have the permission to change the field {field} on this instance of model"
|
_("You don't have the permission to change the field {field} on this instance of model"
|
||||||
" {app_label}.{model_name}.")
|
" {app_label}.{model_name}.")
|
||||||
@ -66,7 +76,7 @@ def pre_save_object(sender, instance, **kwargs):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# We check if the user has right to add the object
|
# We check if the user has right to add the object
|
||||||
has_perm = PermissionBackend.check_perm(user, app_label + ".add_" + model_name, instance)
|
has_perm = PermissionBackend.check_perm(request, app_label + ".add_" + model_name, instance)
|
||||||
|
|
||||||
if not has_perm:
|
if not has_perm:
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
@ -87,8 +97,8 @@ def pre_delete_object(instance, **kwargs):
|
|||||||
# Don't check permissions on force-deleted objects
|
# Don't check permissions on force-deleted objects
|
||||||
return
|
return
|
||||||
|
|
||||||
user = get_current_authenticated_user()
|
request = get_current_request()
|
||||||
if user is None:
|
if request is None:
|
||||||
# Action performed on shell is always granted
|
# Action performed on shell is always granted
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -97,7 +107,7 @@ def pre_delete_object(instance, **kwargs):
|
|||||||
model_name = model_name_full[1]
|
model_name = model_name_full[1]
|
||||||
|
|
||||||
# We check if the user has rights to delete the object
|
# We check if the user has rights to delete the object
|
||||||
if not PermissionBackend.check_perm(user, app_label + ".delete_" + model_name, instance):
|
if not PermissionBackend.check_perm(request, app_label + ".delete_" + model_name, instance):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
_("You don't have the permission to delete this instance of model {app_label}.{model_name}.")
|
_("You don't have the permission to delete this instance of model {app_label}.{model_name}.")
|
||||||
.format(app_label=app_label, model_name=model_name))
|
.format(app_label=app_label, model_name=model_name))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
@ -8,7 +8,7 @@ from django.urls import reverse_lazy
|
|||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django_tables2 import A
|
from django_tables2 import A
|
||||||
from member.models import Membership
|
from member.models import Membership
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class RightsTable(tables.Table):
|
|||||||
def render_user(self, value):
|
def render_user(self, value):
|
||||||
# If the user has the right, link the displayed user with the page of its detail.
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
s = value.username
|
s = value.username
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value):
|
||||||
s = format_html("<a href={url}>{name}</a>",
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
return s
|
return s
|
||||||
@ -28,7 +28,7 @@ class RightsTable(tables.Table):
|
|||||||
def render_club(self, value):
|
def render_club(self, value):
|
||||||
# If the user has the right, link the displayed user with the page of its detail.
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
s = value.name
|
s = value.name
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value):
|
if PermissionBackend.check_perm(get_current_request(), "member.view_club", value):
|
||||||
s = format_html("<a href={url}>{name}</a>",
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class RightsTable(tables.Table):
|
|||||||
| Q(name="Bureau de club"))
|
| Q(name="Bureau de club"))
|
||||||
& Q(weirole__isnull=True))).all()
|
& Q(weirole__isnull=True))).all()
|
||||||
s = ", ".join(str(role) for role in roles)
|
s = ", ".join(str(role) for role in roles)
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record):
|
if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record):
|
||||||
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
||||||
+ "'>" + s + "</a>")
|
+ "'>" + s + "</a>")
|
||||||
return s
|
return s
|
||||||
|
74
apps/permission/templates/permission/scopes.html
Normal file
74
apps/permission/templates/permission/scopes.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h2>{% trans "Available scopes" %}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="accordionApps">
|
||||||
|
{% for app, app_scopes in scopes.items %}
|
||||||
|
<div class="card">
|
||||||
|
<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.lower }}" aria-expanded="false"
|
||||||
|
aria-controls="app-{{ app.name.lower }}">
|
||||||
|
{{ app.name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<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.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.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>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p>
|
||||||
|
{% trans "No applications defined" %}.
|
||||||
|
<a href="{% url 'oauth2_provider:register' %}">{% trans "Click here" %}</a> {% trans "if you want to register a new one" %}.
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
{% for app in scopes.keys %}
|
||||||
|
let elements = document.getElementsByName("scope-{{ app.name.lower }}");
|
||||||
|
for (let element of elements) {
|
||||||
|
element.onchange = function (event) {
|
||||||
|
let scope = ""
|
||||||
|
for (let element of elements) {
|
||||||
|
if (element.checked) {
|
||||||
|
scope += element.value + " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope = scope.substr(0, scope.length - 1)
|
||||||
|
|
||||||
|
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>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -1,12 +1,12 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
from django import template
|
from django import template
|
||||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
from note_kfet.middlewares import get_current_request
|
||||||
from permission.backends import PermissionBackend
|
|
||||||
|
from ..backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
@ -14,9 +14,10 @@ def not_empty_model_list(model_name):
|
|||||||
"""
|
"""
|
||||||
Return True if and only if the current user has right to see any object of the given model.
|
Return True if and only if the current user has right to see any object of the given model.
|
||||||
"""
|
"""
|
||||||
user = get_current_authenticated_user()
|
request = get_current_request()
|
||||||
session = get_current_session()
|
user = request.user
|
||||||
if user is None or isinstance(user, AnonymousUser):
|
session = request.session
|
||||||
|
if user is None or not user.is_authenticated:
|
||||||
return False
|
return False
|
||||||
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
||||||
return True
|
return True
|
||||||
@ -29,11 +30,12 @@ def model_list(model_name, t="view", fetch=True):
|
|||||||
"""
|
"""
|
||||||
Return the queryset of all visible instances of the given model.
|
Return the queryset of all visible instances of the given model.
|
||||||
"""
|
"""
|
||||||
user = get_current_authenticated_user()
|
request = get_current_request()
|
||||||
|
user = request.user
|
||||||
spl = model_name.split(".")
|
spl = model_name.split(".")
|
||||||
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
|
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
|
||||||
qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, t))
|
qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(request, ct, t))
|
||||||
if user is None or isinstance(user, AnonymousUser):
|
if user is None or not user.is_authenticated:
|
||||||
return qs.none()
|
return qs.none()
|
||||||
if fetch:
|
if fetch:
|
||||||
qs = qs.all()
|
qs = qs.all()
|
||||||
@ -49,7 +51,7 @@ def model_list_length(model_name, t="view"):
|
|||||||
|
|
||||||
|
|
||||||
def has_perm(perm, obj):
|
def has_perm(perm, obj):
|
||||||
return PermissionBackend.check_perm(get_current_authenticated_user(), perm, obj)
|
return PermissionBackend.check_perm(get_current_request(), perm, obj)
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
94
apps/permission/tests/test_oauth2.py
Normal file
94
apps/permission/tests/test_oauth2.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
|
from member.models import Membership, Club
|
||||||
|
from note.models import NoteUser
|
||||||
|
from oauth2_provider.models import Application, AccessToken
|
||||||
|
|
||||||
|
from ..models import Role, Permission
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2TestCase(TestCase):
|
||||||
|
fixtures = ('initial', )
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create(
|
||||||
|
username="toto",
|
||||||
|
)
|
||||||
|
self.application = Application.objects.create(
|
||||||
|
name="Test",
|
||||||
|
client_type=Application.CLIENT_PUBLIC,
|
||||||
|
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
|
||||||
|
user=self.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_oauth2_access(self):
|
||||||
|
"""
|
||||||
|
Create a simple OAuth2 access token that only has the right to see data of the current user
|
||||||
|
and check that this token has required access, and nothing more.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bde = Club.objects.get(name="BDE")
|
||||||
|
view_user_perm = Permission.objects.get(pk=1) # View own user detail
|
||||||
|
|
||||||
|
# Create access token that has access to our own user detail
|
||||||
|
token = AccessToken.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
application=self.application,
|
||||||
|
scope=f"{view_user_perm.pk}_{bde.pk}",
|
||||||
|
token=get_random_string(64),
|
||||||
|
expires=timezone.now() + timedelta(days=365),
|
||||||
|
)
|
||||||
|
|
||||||
|
# No access without token
|
||||||
|
resp = self.client.get(f'/api/user/{self.user.pk}/')
|
||||||
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
# Valid token but user has no membership, so the query is not returning the user object
|
||||||
|
resp = self.client.get(f'/api/user/{self.user.pk}/', **{'Authorization': f'Bearer {token.token}'})
|
||||||
|
self.assertEqual(resp.status_code, 404)
|
||||||
|
|
||||||
|
# Create membership to validate permissions
|
||||||
|
NoteUser.objects.create(user=self.user)
|
||||||
|
membership = Membership.objects.create(user=self.user, club_id=bde.pk)
|
||||||
|
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
# User is now a member and can now see its own user detail
|
||||||
|
resp = self.client.get(f'/api/user/{self.user.pk}/', **{'Authorization': f'Bearer {token.token}'})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
# Token is not granted to see profile detail
|
||||||
|
resp = self.client.get(f'/api/members/profile/{self.user.profile.pk}/',
|
||||||
|
**{'Authorization': f'Bearer {token.token}'})
|
||||||
|
self.assertEqual(resp.status_code, 404)
|
||||||
|
|
||||||
|
def test_scopes(self):
|
||||||
|
"""
|
||||||
|
Ensure that the scopes page is loading.
|
||||||
|
"""
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
resp = self.client.get(reverse('permission:scopes'))
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(self.application, resp.context['scopes'])
|
||||||
|
self.assertNotIn('1_1', resp.context['scopes'][self.application]) # The user has not this permission
|
||||||
|
|
||||||
|
# Create membership to validate permissions
|
||||||
|
bde = Club.objects.get(name="BDE")
|
||||||
|
NoteUser.objects.create(user=self.user)
|
||||||
|
membership = Membership.objects.create(user=self.user, club_id=bde.pk)
|
||||||
|
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
resp = self.client.get(reverse('permission:scopes'))
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(self.application, resp.context['scopes'])
|
||||||
|
self.assertIn('1_1', resp.context['scopes'][self.application]) # Now the user has this permission
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user