feat(dgsi): Add a list of accounts and allow managin the assigned VLAN
This commit is contained in:
parent
e43e42afed
commit
b1d80b3837
7 changed files with 127 additions and 14 deletions
|
@ -1,5 +1,7 @@
|
|||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.http import HttpRequest
|
||||
from django.views.generic.base import ContextMixin, TemplateResponseMixin
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from dgsi.models import User
|
||||
|
||||
|
@ -18,3 +20,24 @@ class StaffRequiredMixin(UserPassesTestMixin):
|
|||
def get_context_data(self, **kwargs):
|
||||
# NOTE: We are only allowed to do this if a class is supplied to the right when constructing the view
|
||||
return super().get_context_data(admin_view=True, **kwargs) # pyright: ignore
|
||||
|
||||
|
||||
class HtmxPostMixin(TemplateResponseMixin, ContextMixin):
|
||||
http_method_names = ["post"]
|
||||
|
||||
def execute_action(self, *args, **kwargs) -> None:
|
||||
return None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Execute action
|
||||
self.execute_action()
|
||||
|
||||
context = self.get_context_data(**kwargs)
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class HtmxPostObjectMixin(SingleObjectMixin, HtmxPostMixin):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
|
|
@ -235,7 +235,7 @@ class User(AbstractUser):
|
|||
|
||||
self.vlan_id = min(
|
||||
set(range(settings.VLAN_ID_MIN, settings.VLAN_ID_MAX))
|
||||
- set(User.objects.exclude(vlan_id__isnull=True).values_list("vlan_id"))
|
||||
- set(*User.objects.exclude(vlan_id__isnull=True).values_list("vlan_id"))
|
||||
)
|
||||
|
||||
# Preempt the vlan attribution
|
||||
|
@ -270,7 +270,7 @@ class User(AbstractUser):
|
|||
|
||||
if group.member != [f"{self.username}@sso.dgnum.eu"]:
|
||||
# Remove the user from the group
|
||||
sync_call("group_delete_members", self.username)
|
||||
sync_call("group_delete_members", group_name, [self.username])
|
||||
self.vlan_id = None
|
||||
self.save(update_fields=["vlan_id"])
|
||||
raise RuntimeError("Duplicate VLAN attribution detected")
|
||||
|
|
19
src/dgsi/templates/dgsi/partials/user_list-user.html
Normal file
19
src/dgsi/templates/dgsi/partials/user_list-user.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% load i18n %}
|
||||
|
||||
<tr id="user-{{ person.pk }}">
|
||||
<th>{{ person.username }}</th>
|
||||
<td>{{ person.first_name }} {{ person.last_name }}</td>
|
||||
<td>{{ person.email }}</td>
|
||||
<td>{{ person.vlan_id|default:"" }}</td>
|
||||
<td>
|
||||
{% if person.vlan_id %}
|
||||
<a hx-post="{% url "dgsi:dgn-user_deassign_vlan" person.pk %}"
|
||||
hx-target="#user-{{ person.pk }}"
|
||||
class="button is-fullwidth is-light is-warning">{% trans "Désallouer" %}</a>
|
||||
{% else %}
|
||||
<a hx-post="{% url "dgsi:dgn-user_assign_vlan" person.pk %}"
|
||||
hx-target="#user-{{ person.pk }}"
|
||||
class="button is-fullwidth is-light is-primary">{% trans "Allouer" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
25
src/dgsi/templates/dgsi/user_list.html
Normal file
25
src/dgsi/templates/dgsi/user_list.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% include "_subtitle.html" with subtitle="Comptes DG·SI" %}
|
||||
|
||||
<table class="table is-fullwidth is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Nom d'utilisateur" %}</th>
|
||||
<th>{% trans "Nom d'usage" %}</th>
|
||||
<th>{% trans "Adresse e-mail" %}</th>
|
||||
<th>{% trans "VLAN attribué" %}</th>
|
||||
<th>{% trans "Gestion du VLAN" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in user_list %}
|
||||
{% include "dgsi/partials/user_list-user.html" %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock content %}
|
|
@ -53,6 +53,21 @@ urlpatterns = [
|
|||
views.CreateKanidmAccountView.as_view(),
|
||||
name="dgn-create_kanidm_user",
|
||||
),
|
||||
path(
|
||||
"accounts/list/",
|
||||
views.UserListView.as_view(),
|
||||
name="dgn-user_list",
|
||||
),
|
||||
path(
|
||||
"accounts/assign-vlan/<int:pk>",
|
||||
views.AssignVlanView.as_view(),
|
||||
name="dgn-user_assign_vlan",
|
||||
),
|
||||
path(
|
||||
"accounts/deassign-vlan/<int:pk>",
|
||||
views.DeassignVlanView.as_view(),
|
||||
name="dgn-user_deassign_vlan",
|
||||
),
|
||||
path(
|
||||
"accounts/forbidden/",
|
||||
views.TemplateView.as_view(template_name="accounts/forbidden_category.html"),
|
||||
|
|
|
@ -17,7 +17,7 @@ from django.views.generic import FormView, ListView, RedirectView, TemplateView,
|
|||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from dgsi.forms import CreateKanidmAccountForm, CreateSelfAccountForm
|
||||
from dgsi.mixins import StaffRequiredMixin
|
||||
from dgsi.mixins import HtmxPostObjectMixin, StaffRequiredMixin
|
||||
from dgsi.models import Archive, Bylaws, Service, Statutes, User
|
||||
from shared.kanidm import klient
|
||||
|
||||
|
@ -51,6 +51,7 @@ ADMIN_LINKS: list[Link] = [
|
|||
_("Créer un compte Kanidm"),
|
||||
"user-plus",
|
||||
),
|
||||
Link("is-primary", "dgsi:dgn-user_list", _("Liste des comptes"), "users"),
|
||||
Link(
|
||||
"is-warning", "admin:index", _("Interface d'administration"), "settings-filled"
|
||||
),
|
||||
|
@ -371,3 +372,25 @@ class CreateKanidmAccountView(StaffRequiredMixin, SuccessMessageMixin, FormView)
|
|||
).send()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UserListView(StaffRequiredMixin, ListView):
|
||||
model = User
|
||||
|
||||
|
||||
class AssignVlanView(StaffRequiredMixin, HtmxPostObjectMixin, View):
|
||||
model = User
|
||||
template_name = "dgsi/partials/user_list-user.html"
|
||||
context_object_name = "person"
|
||||
|
||||
def execute_action(self, *args, **kwargs) -> None:
|
||||
self.object.register_unique_vlan()
|
||||
|
||||
|
||||
class DeassignVlanView(StaffRequiredMixin, HtmxPostObjectMixin, View):
|
||||
model = User
|
||||
template_name = "dgsi/partials/user_list-user.html"
|
||||
context_object_name = "person"
|
||||
|
||||
def execute_action(self, *args, **kwargs) -> None:
|
||||
self.object.reclaim_vlan()
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
(document.querySelectorAll(".notification .delete") || []).forEach(
|
||||
($delete) => {
|
||||
const $notification = $delete.parentNode;
|
||||
const dismiss = () => $notification.parentNode.removeChild($notification);
|
||||
const init = ($node) => {
|
||||
const q = (query, f) => ($node.querySelectorAll(query) || []).forEach(f);
|
||||
|
||||
$delete.addEventListener("click", dismiss);
|
||||
setTimeout(dismiss, 15000);
|
||||
},
|
||||
);
|
||||
q(".notification .delete", ($delete) => {
|
||||
const $notification = $delete.parentNode;
|
||||
const dismiss = () => $notification.parentNode.removeChild($notification);
|
||||
|
||||
(document.querySelectorAll("[data-toggle]") || []).forEach(($toggle) => {
|
||||
$delete.addEventListener("click", dismiss);
|
||||
setTimeout(dismiss, 15000);
|
||||
});
|
||||
|
||||
q("[data-toggle]", ($toggle) => {
|
||||
const target = document.querySelector($toggle.dataset.target);
|
||||
|
||||
$toggle.addEventListener("click", () => {
|
||||
|
@ -25,9 +25,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
});
|
||||
|
||||
(document.querySelectorAll("[data-select]") || []).forEach(($input) => {
|
||||
q("[data-select]", ($input) => {
|
||||
$input.addEventListener("click", () => {
|
||||
$input.select();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
init(document);
|
||||
|
||||
document.body.addEventListener("htmx:load", (e) => {
|
||||
init(e.detail.elt);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue