mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-22 11:23:10 +00:00
Merge branch 'fix-98-new-edit-listright' into 'dev'
Fix #98 See merge request re2o/re2o!507
This commit is contained in:
commit
e94b619116
4 changed files with 196 additions and 70 deletions
|
@ -213,3 +213,17 @@ def remove_user_room(room, force=True):
|
||||||
if force or not user.has_access():
|
if force or not user.has_access():
|
||||||
user.room = None
|
user.room = None
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
def permission_tree(queryset=None):
|
||||||
|
r = {}
|
||||||
|
permissions = queryset or Permission.objects.all()
|
||||||
|
for p in permissions:
|
||||||
|
key, app, model = p.natural_key()
|
||||||
|
name = p.name
|
||||||
|
if app not in r:
|
||||||
|
r[app] = {}
|
||||||
|
if model not in r[app]:
|
||||||
|
r[app][model] = {}
|
||||||
|
r[app][model][key] = p
|
||||||
|
return r
|
||||||
|
|
|
@ -48,6 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script>
|
<script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script>
|
||||||
<script src="{% static 'js/collapse-from-url.js' %}"></script>
|
<script src="{% static 'js/collapse-from-url.js' %}"></script>
|
||||||
|
|
||||||
|
{% block custom_js %}{% endblock %}
|
||||||
|
|
||||||
{# Load CSS #}
|
{# Load CSS #}
|
||||||
{% bootstrap_css %}
|
{% bootstrap_css %}
|
||||||
<link href="{% static 'css/typeaheadjs.css' %}" rel="stylesheet">
|
<link href="{% static 'css/typeaheadjs.css' %}" rel="stylesheet">
|
||||||
|
|
137
users/templates/users/edit_listright.html
Normal file
137
users/templates/users/edit_listright.html
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
{% extends 'users/sidebar.html' %}
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Lara Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Users" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_js %}
|
||||||
|
<!-- first import Vue -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
|
||||||
|
<!-- import JavaScript -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/liquor-tree/dist/liquor-tree.umd.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_field form.name %}
|
||||||
|
{% bootstrap_field form.unix_name %}
|
||||||
|
{% if form.gid %}
|
||||||
|
{% bootstrap_field form.gid %}
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_field form.critical %}
|
||||||
|
{% bootstrap_field form.details %}
|
||||||
|
<div id="treeapp" style="display: none;">
|
||||||
|
<label class="control-label">Permissions</label>
|
||||||
|
<tree
|
||||||
|
:data="treeData"
|
||||||
|
:options="treeOptions"
|
||||||
|
@node:checked="onNodeChecked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="legacy_form">
|
||||||
|
{% bootstrap_field form.permissions %}
|
||||||
|
</div>
|
||||||
|
{% bootstrap_button action_name button_type="submit" icon='ok' button_class='btn-success' %}
|
||||||
|
</form>
|
||||||
|
{% if load_js_file %}
|
||||||
|
<script src="{{ load_js_file }}"></script>
|
||||||
|
{% endif %}
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#treeapp',
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
treeData: this.getData(),
|
||||||
|
treeOptions: {
|
||||||
|
checkbox: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
document.getElementById('legacy_form').style.display='none';
|
||||||
|
document.getElementById('treeapp').style.display='block';
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
return [
|
||||||
|
{% for app,models in permissions.items %}
|
||||||
|
{
|
||||||
|
text: '{{ app }}',
|
||||||
|
state: { expanded: false },
|
||||||
|
children: [
|
||||||
|
{% for model,keys in models.items %}
|
||||||
|
{
|
||||||
|
text:'{{model}}',
|
||||||
|
children:[
|
||||||
|
{% for key,permission in keys.items %}
|
||||||
|
{
|
||||||
|
text: '{{ key }} : {{ permission.name }}',
|
||||||
|
{% if permission in instance.permissions.all %}
|
||||||
|
state: { checked: true },
|
||||||
|
{% else %}
|
||||||
|
state: { checked: false },
|
||||||
|
{% endif %}
|
||||||
|
data: {
|
||||||
|
checkbox_value: '{{permission.id}}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodeChecked(node) {
|
||||||
|
if ("checkbox_value" in node.data) {
|
||||||
|
var selector = '#id_ListRight-permissions input[value="'.concat(node.data.checkbox_value, '"]');
|
||||||
|
document.querySelector(selector).checked=true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNodeUnchecked(node) {
|
||||||
|
if ("checkbox_value" in node.data) {
|
||||||
|
var selector = '#id_ListRight-permissions input[value="'.concat(node.data.checkbox_value, '"]');
|
||||||
|
document.querySelector(selector).checked=false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
113
users/views.py
113
users/views.py
|
@ -61,7 +61,7 @@ from preferences.models import OptionalUser, GeneralOption, AssoOption
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from re2o.settings_local import OPTIONNAL_APPS_RE2O
|
from re2o.settings_local import OPTIONNAL_APPS_RE2O
|
||||||
from re2o.views import form
|
from re2o.views import form
|
||||||
from re2o.utils import all_has_access
|
from re2o.utils import all_has_access, permission_tree
|
||||||
from re2o.base import re2o_paginator, SortTable
|
from re2o.base import re2o_paginator, SortTable
|
||||||
from re2o.acl import (
|
from re2o.acl import (
|
||||||
can_create,
|
can_create,
|
||||||
|
@ -122,7 +122,9 @@ def new_user(request):
|
||||||
|
|
||||||
GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up")
|
GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up")
|
||||||
GTU = GeneralOption.get_cached_value("GTU")
|
GTU = GeneralOption.get_cached_value("GTU")
|
||||||
is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation")
|
is_set_password_allowed = OptionalUser.get_cached_value(
|
||||||
|
"allow_set_password_during_user_creation"
|
||||||
|
)
|
||||||
|
|
||||||
if user.is_valid():
|
if user.is_valid():
|
||||||
user = user.save()
|
user = user.save()
|
||||||
|
@ -199,11 +201,7 @@ def edit_club_admin_members(request, club_instance, **_kwargs):
|
||||||
reverse("users:profil", kwargs={"userid": str(club_instance.id)})
|
reverse("users:profil", kwargs={"userid": str(club_instance.id)})
|
||||||
)
|
)
|
||||||
return form(
|
return form(
|
||||||
{
|
{"userform": club, "showCGU": False, "action_name": _("Edit"),},
|
||||||
"userform": club,
|
|
||||||
"showCGU": False,
|
|
||||||
"action_name": _("Edit"),
|
|
||||||
},
|
|
||||||
"users/user.html",
|
"users/user.html",
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
@ -233,9 +231,7 @@ def edit_info(request, user, userid):
|
||||||
|
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
||||||
return form(
|
return form(
|
||||||
{"userform": user_form, "action_name": _("Edit")},
|
{"userform": user_form, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,12 +245,12 @@ def state(request, user, userid):
|
||||||
user_instance = state_form.save()
|
user_instance = state_form.save()
|
||||||
messages.success(request, _("The states were edited."))
|
messages.success(request, _("The states were edited."))
|
||||||
if user_instance.trigger_email_changed_state(request):
|
if user_instance.trigger_email_changed_state(request):
|
||||||
messages.success(request, _("An email to confirm the address was sent."))
|
messages.success(
|
||||||
|
request, _("An email to confirm the address was sent.")
|
||||||
|
)
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
||||||
return form(
|
return form(
|
||||||
{"userform": state_form, "action_name": _("Edit")},
|
{"userform": state_form, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,9 +265,7 @@ def groups(request, user, userid):
|
||||||
messages.success(request, _("The groups were edited."))
|
messages.success(request, _("The groups were edited."))
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
||||||
return form(
|
return form(
|
||||||
{"userform": group_form, "action_name": _("Edit")},
|
{"userform": group_form, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -324,9 +318,7 @@ def new_serviceuser(request):
|
||||||
messages.success(request, _("The service user was created."))
|
messages.success(request, _("The service user was created."))
|
||||||
return redirect(reverse("users:index-serviceusers"))
|
return redirect(reverse("users:index-serviceusers"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": user, "action_name": _("Add")},
|
{"userform": user, "action_name": _("Add")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -341,9 +333,7 @@ def edit_serviceuser(request, serviceuser, **_kwargs):
|
||||||
messages.success(request, _("The service user was edited."))
|
messages.success(request, _("The service user was edited."))
|
||||||
return redirect(reverse("users:index-serviceusers"))
|
return redirect(reverse("users:index-serviceusers"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": serviceuser, "action_name": _("Edit")},
|
{"userform": serviceuser, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -379,9 +369,7 @@ def add_ban(request, user, userid):
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
||||||
if user.is_ban():
|
if user.is_ban():
|
||||||
messages.error(request, _("Warning: this user already has an active ban."))
|
messages.error(request, _("Warning: this user already has an active ban."))
|
||||||
return form(
|
return form({"userform": ban, "action_name": _("Add")}, "users/user.html", request)
|
||||||
{"userform": ban, "action_name": _("Add")}, "users/user.html", request
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -398,9 +386,7 @@ def edit_ban(request, ban_instance, **_kwargs):
|
||||||
ban.save()
|
ban.save()
|
||||||
messages.success(request, _("The ban was edited."))
|
messages.success(request, _("The ban was edited."))
|
||||||
return redirect(reverse("users:index"))
|
return redirect(reverse("users:index"))
|
||||||
return form(
|
return form({"userform": ban, "action_name": _("Edit")}, "users/user.html", request)
|
||||||
{"userform": ban, "action_name": _("Edit")}, "users/user.html", request
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -433,9 +419,7 @@ def add_whitelist(request, user, userid):
|
||||||
request, _("Warning: this user already has an active whitelist.")
|
request, _("Warning: this user already has an active whitelist.")
|
||||||
)
|
)
|
||||||
return form(
|
return form(
|
||||||
{"userform": whitelist, "action_name": _("Add")},
|
{"userform": whitelist, "action_name": _("Add")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -453,9 +437,7 @@ def edit_whitelist(request, whitelist_instance, **_kwargs):
|
||||||
messages.success(request, _("The whitelist was edited."))
|
messages.success(request, _("The whitelist was edited."))
|
||||||
return redirect(reverse("users:index"))
|
return redirect(reverse("users:index"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": whitelist, "action_name": _("Edit")},
|
{"userform": whitelist, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -488,11 +470,7 @@ def add_emailaddress(request, user, userid):
|
||||||
messages.success(request, _("The local email account was created."))
|
messages.success(request, _("The local email account was created."))
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
return redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
|
||||||
return form(
|
return form(
|
||||||
{
|
{"userform": emailaddress, "showCGU": False, "action_name": _("Add"),},
|
||||||
"userform": emailaddress,
|
|
||||||
"showCGU": False,
|
|
||||||
"action_name": _("Add"),
|
|
||||||
},
|
|
||||||
"users/user.html",
|
"users/user.html",
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
@ -515,11 +493,7 @@ def edit_emailaddress(request, emailaddress_instance, **_kwargs):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return form(
|
return form(
|
||||||
{
|
{"userform": emailaddress, "showCGU": False, "action_name": _("Edit"),},
|
||||||
"userform": emailaddress,
|
|
||||||
"showCGU": False,
|
|
||||||
"action_name": _("Edit"),
|
|
||||||
},
|
|
||||||
"users/user.html",
|
"users/user.html",
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
@ -555,7 +529,9 @@ def edit_email_settings(request, user_instance, **_kwargs):
|
||||||
messages.success(request, _("The email settings were edited."))
|
messages.success(request, _("The email settings were edited."))
|
||||||
|
|
||||||
if user_instance.send_confirm_email_if_necessary(request):
|
if user_instance.send_confirm_email_if_necessary(request):
|
||||||
messages.success(request, _("An email to confirm your address was sent."))
|
messages.success(
|
||||||
|
request, _("An email to confirm your address was sent.")
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
reverse("users:profil", kwargs={"userid": str(user_instance.id)})
|
reverse("users:profil", kwargs={"userid": str(user_instance.id)})
|
||||||
|
@ -583,9 +559,7 @@ def add_school(request):
|
||||||
messages.success(request, _("The school was added."))
|
messages.success(request, _("The school was added."))
|
||||||
return redirect(reverse("users:index-school"))
|
return redirect(reverse("users:index-school"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": school, "action_name": _("Add")},
|
{"userform": school, "action_name": _("Add")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -601,9 +575,7 @@ def edit_school(request, school_instance, **_kwargs):
|
||||||
messages.success(request, _("The school was edited."))
|
messages.success(request, _("The school was edited."))
|
||||||
return redirect(reverse("users:index-school"))
|
return redirect(reverse("users:index-school"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": school, "action_name": _("Edit")},
|
{"userform": school, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -661,9 +633,7 @@ def edit_shell(request, shell_instance, **_kwargs):
|
||||||
messages.success(request, _("The shell was edited."))
|
messages.success(request, _("The shell was edited."))
|
||||||
return redirect(reverse("users:index-shell"))
|
return redirect(reverse("users:index-shell"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": shell, "action_name": _("Edit")},
|
{"userform": shell, "action_name": _("Edit")}, "users/user.html", request,
|
||||||
"users/user.html",
|
|
||||||
request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -675,7 +645,9 @@ def del_shell(request, shell, **_kwargs):
|
||||||
shell.delete()
|
shell.delete()
|
||||||
messages.success(request, _("The shell was deleted."))
|
messages.success(request, _("The shell was deleted."))
|
||||||
return redirect(reverse("users:index-shell"))
|
return redirect(reverse("users:index-shell"))
|
||||||
return form({"objet": shell, "objet_name": _("shell")}, "users/delete.html", request)
|
return form(
|
||||||
|
{"objet": shell, "objet_name": _("shell")}, "users/delete.html", request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -689,8 +661,8 @@ def add_listright(request):
|
||||||
messages.success(request, _("The group of rights was added."))
|
messages.success(request, _("The group of rights was added."))
|
||||||
return redirect(reverse("users:index-listright"))
|
return redirect(reverse("users:index-listright"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": listright, "action_name": _("Add")},
|
{"form": listright, "action_name": _("Add"), "permissions": permission_tree()},
|
||||||
"users/user.html",
|
"users/edit_listright.html",
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -700,15 +672,20 @@ def add_listright(request):
|
||||||
def edit_listright(request, listright_instance, **_kwargs):
|
def edit_listright(request, listright_instance, **_kwargs):
|
||||||
""" Editer un groupe/droit, necessite droit bureau,
|
""" Editer un groupe/droit, necessite droit bureau,
|
||||||
à partir du listright id """
|
à partir du listright id """
|
||||||
listright = ListRightForm(request.POST or None, instance=listright_instance)
|
listright_form = ListRightForm(request.POST or None, instance=listright_instance)
|
||||||
if listright.is_valid():
|
if listright_form.is_valid():
|
||||||
if listright.changed_data:
|
if listright_form.changed_data:
|
||||||
listright.save()
|
listright_form.save()
|
||||||
messages.success(request, _("The group of rights was edited."))
|
messages.success(request, _("The group of rights was edited."))
|
||||||
return redirect(reverse("users:index-listright"))
|
return redirect(reverse("users:index-listright"))
|
||||||
return form(
|
return form(
|
||||||
{"userform": listright, "action_name": _("Edit")},
|
{
|
||||||
"users/user.html",
|
"form": listright_form,
|
||||||
|
"action_name": _("Edit"),
|
||||||
|
"permissions": permission_tree(),
|
||||||
|
"instance": listright_instance,
|
||||||
|
},
|
||||||
|
"users/edit_listright.html",
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1063,7 +1040,7 @@ def process_email(request, req):
|
||||||
return form(
|
return form(
|
||||||
{"email": user.email, "name": user.get_full_name()},
|
{"email": user.email, "name": user.get_full_name()},
|
||||||
"users/confirm_email.html",
|
"users/confirm_email.html",
|
||||||
request
|
request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1084,11 +1061,7 @@ def resend_confirmation_email(request, logged_user, userid):
|
||||||
messages.success(request, _("An email to confirm your address was sent."))
|
messages.success(request, _("An email to confirm your address was sent."))
|
||||||
return redirect(reverse("users:profil", kwargs={"userid": userid}))
|
return redirect(reverse("users:profil", kwargs={"userid": userid}))
|
||||||
|
|
||||||
return form(
|
return form({"email": user.email}, "users/resend_confirmation_email.html", request)
|
||||||
{"email": user.email},
|
|
||||||
"users/resend_confirmation_email.html",
|
|
||||||
request
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|
Loading…
Reference in a new issue