Compare commits

...

18 commits

Author SHA1 Message Date
Basile Clement
25a1b880e5 Merge branch 'thubrecht/django3' into 'master'
Update to Django 3.2

See merge request klub-dev-ens/WikiENS!18
2022-12-05 13:57:57 +01:00
d98d8d013c Update to Django 3.2 2022-01-07 16:47:09 +01:00
Martin Pepin
dbffa735a0 Merge branch 'thubrecht/scroll-groups' into 'master'
On évite le graphe qui prend 3 écrans de largeur

See merge request klub-dev-ens/WikiENS!16
2021-10-30 10:59:57 +02:00
4262f0e7a5 On évite le graphe qui prend 3 écrans de largeur 2021-10-29 17:51:21 +02:00
Martin Pepin
9ce3238000 Merge branch 'kerl/group_list' into 'master'
List des groupes

See merge request klub-dev-ens/WikiENS!11
2021-10-26 20:12:27 +02:00
29c5abbc1a On affiche la liste des managers 2021-10-26 20:01:58 +02:00
Martin Pépin
cc50d540c3 Display the list of groups at /_groups/ 2021-10-26 19:48:33 +02:00
Martin Pepin
ef00b63053 Merge branch 'thubrecht/groupes' into 'master'
Ajoute la gestion des groupes

See merge request klub-dev-ens/WikiENS!15
2021-10-26 19:41:30 +02:00
c771f1ad49 Placeholder 2021-10-26 19:38:00 +02:00
66cb88721f Docstring 2021-10-26 19:29:30 +02:00
e8830540ec Meilleur message d'erreur 2021-10-26 19:20:29 +02:00
93792f5f46 On évite les erreurs si pas la bonne url 2021-10-26 19:17:06 +02:00
d5c1e2225a On remplace les Querysets par ce qu'on veut 2021-10-26 19:06:23 +02:00
3da477729a Better docstring 2021-10-26 18:57:38 +02:00
9c02e1e902 Use already_notified in get_all_groups 2021-10-26 18:53:31 +02:00
f257209f8f JS update 2021-10-11 15:34:24 +02:00
0fa182cb4a Ajoute la gestion des groupes 2021-07-24 04:13:47 +02:00
Basile Clement
20d0f1fba9 Define HOST to "" to use unix domain socket connection 2021-04-10 20:04:01 +02:00
12 changed files with 695 additions and 102 deletions

View file

@ -109,10 +109,11 @@ DATABASES = {
"NAME": DBNAME,
"USER": DBUSER,
"PASSWORD": DBPASSWD,
"HOST": "localhost",
"HOST": "",
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

View file

@ -1,3 +1,3 @@
Django==2.2.*
Django==3.2.*
git+https://git.eleves.ens.fr/klub-dev-ens/django-allauth-ens.git@1.1.3
wiki==0.7
wiki==0.7.*

View file

@ -1,55 +1,61 @@
{% extends "wiki/base_site.html" %}
{% load i18n staticfiles %}
{% load sekizai_tags %}
{% load i18n static sekizai_tags %}
{% block wiki_site_title %} - WikiENS{% endblock %}
{% block wiki_header_branding %}
<a id="navbar-title" class="navbar-brand" href="/">
Wiki&nbsp;<img src="{% static 'img/logoENS-small.png' %}" style="display:inline-block; height:100%;" />&nbsp;ENS
Wiki&nbsp;<img src="{% static 'img/logoENS-small.png' %}" style="display:inline-block; height:100%;" />&nbsp;ENS
</a>
{% endblock %}
{% block wiki_header_navlinks %}
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link text-primary" href="{% url 'wiki:root' %}">Accueil</a>
</li>
<li class="nav-item">
<a class="nav-link text-primary" href="{% url 'wiki:root' %}">Accueil</a>
</li>
</ul>
<div class="navbar-right">
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" class="dropdown-toggle" data-toggle="dropdown">
{% trans "Paramètres de compte" %}
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url "account_email" %}">
<i class="fa fa-envelope"></i>
{% trans "Email" %}
</a>
<a class="dropdown-item" href="{% url "account_change_password" %}">
<i class="fa fa-lock"></i>
{% trans "Mot de passe" %}
</a>
<a class="dropdown-item" href="{% url "socialaccount_connections" %}" title="Clipper…">
<i class="fa fa-sign-in"></i>
{% trans "Connexions par tiers" %}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url "account_logout" %}">
<i class="fa fa-power-off"></i>
{% trans "Déconnexion" %}
</a>
</div>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url "account_signup" %}">{% trans "Sign Up" %}</a>
</li>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" class="dropdown-toggle" data-toggle="dropdown">
{% trans "Paramètres de compte" %}
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'account_email' %}">
<i class="fa fa-envelope"></i>
{% trans "Email" %}
</a>
<a class="dropdown-item" href="{% url 'account_change_password' %}">
<i class="fa fa-lock"></i>
{% trans "Mot de passe" %}
</a>
<a class="dropdown-item" href="{% url 'socialaccount_connections' %}" title="Clipper…">
<i class="fa fa-sign-in-alt"></i>
{% trans "Connexions par tiers" %}
</a>
{% if request.user.is_staff or request.user.managed_groups.exists %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'wiki_groups:managed-groups' %}">
<i class="fa fa-cog"></i>
{% trans "Liste des groupes gérés" %}
</a>
{% endif %}
</ul>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'account_logout' %}">
<i class="fa fa-power-off"></i>
{% trans "Déconnexion" %}
</a>
</div>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
</li>
{% endif %}
</ul>
</div>
{% endblock %}

52
wiki_groups/forms.py Normal file
View file

@ -0,0 +1,52 @@
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from wiki_groups.models import WikiGroup
User = get_user_model()
class SelectUserForm(forms.Form):
user = forms.CharField(max_length=150)
def clean_user(self):
user = User.objects.filter(username=self.cleaned_data["user"]).first()
if user is None:
self.add_error(
"user", "Aucune utilisatrice ou utilisateur avec ce login n'existe."
)
return user
class SelectGroupForm(forms.Form):
group = forms.CharField(max_length=150)
def clean_group(self):
group = WikiGroup.objects.filter(
django_group__name=self.cleaned_data["group"]
).first()
if group is None:
self.add_error("group", "Aucun groupe avec ce nom n'existe.")
return group
class CreateGroupForm(forms.Form):
group = forms.CharField(max_length=150)
def clean_group(self):
name = self.cleaned_data["group"]
django_group, created = Group.objects.get_or_create(name=name)
if hasattr(django_group, "wikigroup"):
self.add_error("group", "Un groupe avec ce nom existe déjà.")
return None
group = WikiGroup.objects.create(django_group=django_group)
return group

View file

@ -0,0 +1,20 @@
# Generated by Django 2.2.19 on 2021-07-23 09:01
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wiki_groups', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='wikigroup',
name='managers',
field=models.ManyToManyField(blank=True, related_name='managed_groups', to=settings.AUTH_USER_MODEL),
),
]

View file

@ -1,12 +1,14 @@
from django.db import models
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save, m2m_changed
from django.contrib.auth.models import Group as DjangoGroup
from django.db import models
from django.db.models.signals import m2m_changed, post_save
from django.dispatch import receiver
User = get_user_model()
class WikiGroup(models.Model):
""" A structured user group, used to grant permissions on sub-wikis
"""A structured user group, used to grant permissions on sub-wikis
This model contains a structured group of users, in the sense that a group contains
both users and other groups, allowing a DAG group structure.
@ -20,8 +22,8 @@ class WikiGroup(models.Model):
"""
class CyclicStructureException(Exception):
""" Exception raised when a new edge introduces a cycle in the groups'
structure """
"""Exception raised when a new edge introduces a cycle in the groups'
structure"""
def __init__(self, from_group, to_group):
self.from_group = from_group
@ -40,22 +42,54 @@ class WikiGroup(models.Model):
related_name="included_in_groups",
blank=True,
)
users = models.ManyToManyField(get_user_model(), blank=True)
users = models.ManyToManyField(User, blank=True)
managers = models.ManyToManyField(User, related_name="managed_groups", blank=True)
def __str__(self):
return str(self.django_group)
def get_all_users(self):
""" Get the queryset of all the users in this group, including recursively
included users """
"""Get the queryset of all the users in this group, including recursively
included users"""
users_set = self.users.all()
for subgroup in self.includes_groups.all():
users_set = users_set.union(subgroup.get_all_users())
return users_set
def is_manager(self, user):
"""Checks wether the user is a manager of this group or any subgroup"""
# Base case: the user is an explicit manager
if user in self.managers.all():
return True
for subgroup in self.includes_groups.all():
# If the user is a manager of a subgroup
if subgroup.is_manager(user):
return True
return False
def get_all_groups(self, already_notified=None):
"""Returns the set of metagroups i.e. self plus the list of groups including
this group recursively"""
if already_notified is None:
already_notified = set()
elif self.pk in already_notified:
return set()
already_notified.add(self.pk)
groups = {self}
for metagroup in self.included_in_groups.all():
groups |= metagroup.get_all_groups(already_notified=already_notified)
return groups
def propagate_update(self, already_notified=None):
""" Commits itself to the Django group, and calls this method on every group in
`included_in_groups` """
"""Commits itself to the Django group, and calls this method on every group in
`included_in_groups`"""
# Check that we did not already propagate the update signal to this group
if already_notified is None:
@ -69,20 +103,20 @@ class WikiGroup(models.Model):
metagroup.propagate_update(already_notified=already_notified)
def commit_to_django_group(self):
""" Writes this model's data to the related Django group """
"""Writes this model's data to the related Django group"""
self.django_group.user_set.set(self.get_all_users())
def group_in_cycle(self, with_children):
""" Checks whether this group would be in a group cycle if it had
"""Checks whether this group would be in a group cycle if it had
`with_children` as child nodes. This assumes that the graph currently stored is
acyclic.
Returns `None` if no cycle is found, else retuns a child from `with_children`
causing the cycle to appear. """
causing the cycle to appear."""
def do_dfs(cur_node, visited_nodes):
""" DFS to check whether we find `self` again """
"""DFS to check whether we find `self` again"""
if cur_node.pk in visited_nodes:
return False
if cur_node.pk == self.pk:
@ -103,7 +137,7 @@ class WikiGroup(models.Model):
@receiver(post_save, sender=WikiGroup, dispatch_uid="on_wiki_group_changed")
def on_wiki_group_changed(sender, instance, **kwargs):
""" Commit the related WikiGroups to Django Group upon model change """
"""Commit the related WikiGroups to Django Group upon model change"""
instance.propagate_update()
@ -113,8 +147,8 @@ def on_wiki_group_changed(sender, instance, **kwargs):
dispatch_uid="on_wiki_group_includes_changed",
)
def on_wiki_group_includes_changed(sender, instance, action, **kwargs):
""" Commit the related WikiGroups to Django Group upon change of the set of
included other groups """
"""Commit the related WikiGroups to Django Group upon change of the set of
included other groups"""
if action in ["post_add", "post_remove", "post_clear"]:
instance.propagate_update()
@ -125,7 +159,7 @@ def on_wiki_group_includes_changed(sender, instance, action, **kwargs):
dispatch_uid="on_wiki_group_users_changed",
)
def on_wiki_group_users_changed(sender, instance, action, **kwargs):
""" Commit the related WikiGroups to Django Group upon change of included users """
"""Commit the related WikiGroups to Django Group upon change of included users"""
if action in ["post_add", "post_remove", "post_clear"]:
instance.propagate_update()
@ -136,7 +170,7 @@ def on_wiki_group_users_changed(sender, instance, action, **kwargs):
dispatch_uid="on_wiki_group_includes_check_acyclic",
)
def on_wiki_group_includes_check_acyclic(sender, instance, action, pk_set, **kwargs):
""" Checks the acyclicity of the groups' graph before committing new edges.
"""Checks the acyclicity of the groups' graph before committing new edges.
PLEASE NOTE that this check is only a fallback, and that forms should validate
the acyclicity before committing anything.

View file

@ -0,0 +1,207 @@
{% extends "wiki/base.html" %}
{% load sekizai_tags %}
{% block wiki_site_title %}Groupes administrés - WikiENS{% endblock %}
{% block wiki_contents %}
<h2>Gestion du groupe « {{ wikigroup }} »</h2>
<hr>
<div class="container">
<div class="row">
<div class="col">
<h4>Liste des membres</h4>
<br>
<div class="list-group">
<div class="list-group-item">
<form action="{% url 'wiki_groups:add-user' wikigroup.pk %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control {% if errors.user %}is-invalid{% endif %}" name="user" value="{{ errors.user.value }}" placeholder="Ajouter un membre">
<div class="input-group-append">
<button type="submit" class="btn btn-primary rounded-right">Enregistrer</button>
</div>
{% if errors.user %}
{% for msg in errors.user.msg %}
<div class="invalid-feedback">{{ msg }}</div>
{% endfor %}
{% endif %}
</div>
<small class="form-text text-muted">Entrer le login (clipper) de la personne à ajouter</small>
</form>
</div>
{% for user in wikigroup.users.all %}
<div class="list-group-item pb-2">
<span class="font-italic">{{ user }}</span>
<button type="button" class="btn btn-danger btn-sm float-right" data-toggle="modal" data-target="#modal-confirm" data-href="{% url 'wiki_groups:remove-user' wikigroup.pk user.pk %}" data-name="{{ user }}" data-kind="membre">Enlever</a>
</div>
{% endfor %}
</div>
</div>
<div class="col">
<h4>Liste des groupes inclus</h4>
<br>
<div class="list-group">
<div class="list-group-item">
<form action="{% url 'wiki_groups:add-group' wikigroup.pk %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control {% if errors.group_add %}is-invalid{% endif %}" name="group" value="{{ errors.group_add.value }}" placeholder="Ajouter un groupe existant">
<div class="input-group-append">
<button type="submit" class="btn btn-primary rounded-right">Enregistrer</button>
</div>
{% if errors.group_add %}
{% for msg in errors.group_add.msg %}
<div class="invalid-feedback">{{ msg }}</div>
{% endfor %}
{% endif %}
</div>
<small class="form-text text-muted">Entrer le nom du groupe à ajouter</small>
</form>
</div>
{% for group in wikigroup.includes_groups.all %}
<div class="list-group-item pb-2">
<span class="font-italic">{{ group }}</span>
<button type="button" class="btn btn-danger btn-sm float-right" data-toggle="modal" data-target="#modal-confirm" data-href="{% url 'wiki_groups:remove-group' wikigroup.pk group.pk %}" data-name="{{ group }}" data-kind="groupe">Enlever</a>
</div>
{% endfor %}
<div class="list-group-item">
<form action="{% url 'wiki_groups:create-group' wikigroup.pk %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control {% if errors.group_create %}is-invalid{% endif %}" name="group" value="{{ errors.group_create.value }}" placeholder="Créer et ajouter un groupe">
<div class="input-group-append">
<button type="submit" class="btn btn-primary rounded-right">Enregistrer</button>
</div>
{% if errors.group_create %}
{% for msg in errors.group_create.msg %}
<div class="invalid-feedback">{{ msg }}</div>
{% endfor %}
{% endif %}
</div>
<small class="form-text text-muted">Entrer le nom du groupe à créer</small>
</form>
</div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-6">
<h4>Liste des gestionnaires</h4>
<br>
<div class="list-group">
<div class="list-group-item">
<form action="{% url 'wiki_groups:add-manager' wikigroup.pk %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control {% if errors.manager %}is-invalid{% endif %}" name="user" value="{{ errors.manager.value }}" placeholder="Ajouter un·e gestionnaire">
<div class="input-group-append">
<button type="submit" class="btn btn-primary rounded-right">Enregistrer</button>
</div>
{% if errors.manager %}
{% for msg in errors.manager.msg %}
<div class="invalid-feedback">{{ msg }}</div>
{% endfor %}
{% endif %}
</div>
<small class="form-text text-muted">Entrer le login (clipper) de la personne à ajouter</small>
</form>
</div>
{% for user in wikigroup.managers.all %}
<div class="list-group-item pb-2">
<span class="font-italic">{{ user }}</span>
<button type="button" class="btn btn-danger btn-sm float-right" data-toggle="modal" data-target="#modal-confirm" data-href="{% url 'wiki_groups:remove-manager' wikigroup.pk user.pk %}" data-name="{{ user }}" data-kind="gestionnaire">Enlever</a>
</div>
{% endfor %}
</div>
</div>
{% if request.user.is_staff %}
<div class="col">
<h4>Supprimer ce groupe</h4>
<br>
<div class="jumbotron">
<p class="text-danger">Attention, cette action est irréversible.</p>
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#modal-delete">Supprimer</a>
</div>
</div>
<div class="modal fade" id="modal-delete">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Supprimer le groupe {{ wikigroup }} ?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
La suppression du groupe « {{ wikigroup }} » est irréversible et tous ses membres seront enlevés des groupes suivants :
<ul>
{% for g in wikigroup.get_all_groups %}
<li>{{ g }}</li>
{% endfor %}
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<a class="btn btn-danger" href="{% url 'wiki_groups:delete-group' wikigroup.pk %}">Supprimer</a>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
{# Confirmation modal #}
<div class="modal fade" id="modal-confirm">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<a class="btn btn-danger">Enlever</a>
</div>
</div>
</div>
</div>
{% addtoblock "js" %}
<script>
$('#modal-confirm').on('show.bs.modal', function(event) {
const b = $(event.relatedTarget);
const modal = $(this);
modal.find('.modal-title').text(`Enlever un ${b.data('kind')}`);
modal.find('.modal-body').html(`Enlever <span class="font-weight-bold font-italic">${b.data('name')}</span> en tant que ${b.data('kind')} du groupe {{ wikigroup }} ?`);
modal.find('.modal-footer a').prop('href', b.data('href'));
});
</script>
{% endaddtoblock %}
{% endblock %}

View file

@ -1,37 +0,0 @@
{% extends "wiki/base.html" %}
{% load staticfiles %}
{% block wiki_site_title %}Groupes - WikiENS{% endblock %}
{% block wiki_contents %}
<h2>Graphe des groupes du wiki</h2>
<hr />
<div id="svg-graph"></div>
<hr />
<p>
Les flèches représentent l'inclusion : <code>A -&gt; B</code> signifie que
le groupe <code>A</code> est contenu dans le groupe <code>B</code>.
</p>
<script src="{% static 'wiki_groups/js/vendor/viz.js' %}"></script>
<script src="{% static 'wiki_groups/js/vendor/lite.render.js' %}"></script>
<script>
var viz = new Viz();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
viz.renderSVGElement(this.responseText)
.then(element => {
document.getElementById("svg-graph").appendChild(element);
})
}
};
xhttp.open("GET", "{% url 'wiki_groups:dot_graph' %}", true);
xhttp.send();
</script>
{% endblock %}

View file

@ -0,0 +1,52 @@
{% extends "wiki/base.html" %}
{% load static %}
{% block wiki_site_title %}Groupes - WikiENS{% endblock %}
{% block wiki_contents %}
<h2>
Liste des groupes du wiki
{% if request.user.is_staff or request.user.managed_groups.exists %}
<a class="btn btn-primary float-right" href="{% url 'wiki_groups:managed-groups' %}">
Liste des groupes gérés
</a>
{% endif %}
</h2>
<hr>
<ul>
{% for group in wikigroup_list %}
<li>{{ group.django_group.name }} {% if group.managers.exists %}(Géré par {% for m in group.managers.all %}{{ m }}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}</li>
{% endfor %}
</ul>
<h2>Graphe des groupes du wiki</h2>
<hr>
<div id="svg-graph" class="overflow-auto"></div>
<hr>
<p>
Les flèches représentent l'inclusion : <code>A -&gt; B</code> signifie que
le groupe <code>A</code> est contenu dans le groupe <code>B</code>.
</p>
<script src="{% static 'wiki_groups/js/vendor/viz.js' %}"></script>
<script src="{% static 'wiki_groups/js/vendor/lite.render.js' %}"></script>
<script>
var viz = new Viz();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
viz.renderSVGElement(this.responseText)
.then(element => {
document.getElementById("svg-graph").appendChild(element);
})
}
};
xhttp.open('GET', '{% url 'wiki_groups:dot_graph' %}', true);
xhttp.send();
</script>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "wiki/base.html" %}
{% block wiki_site_title %}Groupes administrés - WikiENS{% endblock %}
{% block wiki_contents %}
<h2>Liste des groupes administrés</h2>
<hr>
{% if wikigroup_list %}
<div class="list-group">
{% for group in wikigroup_list %}
<a class="list-group-item list-group-item-action" href="{% url 'wiki_groups:admin-group' group.pk %}">{{ group }}</a>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View file

@ -1,9 +1,31 @@
from django.urls import path
from wiki_groups import views
from wiki_groups import views
app_name = "wiki_groups"
urlpatterns = [
path("", views.GroupList.as_view(), name="list"),
path("dot_graph", views.dot_graph, name="dot_graph"),
path("graph", views.graph, name="graph"),
path("managed", views.ManagedGroupsView.as_view(), name="managed-groups"),
path("<int:pk>", views.WikiGroupView.as_view(), name="admin-group"),
path(
"<int:pk>/rm-user/<int:user_pk>",
views.RemoveUserView.as_view(),
name="remove-user",
),
path(
"<int:pk>/rm-manager/<int:user_pk>",
views.RemoveManagerView.as_view(),
name="remove-manager",
),
path(
"<int:pk>/rm-group/<int:group_pk>",
views.RemoveGroupView.as_view(),
name="remove-group",
),
path("<int:pk>/add-user", views.AddUserView.as_view(), name="add-user"),
path("<int:pk>/add-manager", views.AddManagerView.as_view(), name="add-manager"),
path("<int:pk>/add-group", views.AddGroupView.as_view(), name="add-group"),
path("<int:pk>/create-group", views.CreateGroupView.as_view(), name="create-group"),
path("<int:pk>/delete", views.DeleteGroupView.as_view(), name="delete-group"),
]

View file

@ -1,8 +1,16 @@
from django.http import HttpResponse
from django.views.generic import TemplateView
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.views.generic import DetailView, ListView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import BaseFormView
from wiki_groups.forms import CreateGroupForm, SelectGroupForm, SelectUserForm
from wiki_groups.models import WikiGroup
User = get_user_model()
def dot_graph(request):
response = HttpResponse(content_type="text/vnd.graphviz")
@ -22,4 +30,214 @@ def dot_graph(request):
return response
graph = TemplateView.as_view(template_name="wiki_groups/graph.html")
class GroupList(ListView):
template_name = "wiki_groups/list.html"
model = WikiGroup
def get_queryset(self):
return WikiGroup.objects.select_related("django_group").order_by(
"django_group__name"
)
class WikiGroupMixin(SingleObjectMixin, UserPassesTestMixin):
"""
Restricts the view to a manager of the wikigroup, and selects automatically
the required WikiGroup with its Django group
"""
model = WikiGroup
def get_queryset(self):
return super().get_queryset().select_related("django_group")
def test_func(self):
self.object = self.get_object()
return self.request.user.is_staff or self.object.is_manager(self.request.user)
class StaffMixin(UserPassesTestMixin):
"""Restricts the view to a staff member"""
def test_func(self):
return self.request.user.is_staff
class ManagedGroupsView(LoginRequiredMixin, ListView):
model = WikiGroup
template_name = "wiki_groups/managed.html"
def get_queryset(self):
if self.request.user.is_staff:
return WikiGroup.objects.select_related("django_group")
return self.request.user.managed_groups.select_related("django_group")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
wikigroups = set()
for group in ctx["wikigroup_list"]:
wikigroups |= group.get_all_groups()
ctx["wikigroup_list"] = wikigroups
ctx["object_list"] = wikigroups
return ctx
class WikiGroupView(WikiGroupMixin, DetailView):
template_name = "wiki_groups/admin.html"
def get_context_data(self, **kwargs):
kwargs.update({"errors": self.request.session.pop("wiki_form_errors", None)})
return super().get_context_data(**kwargs)
class RemoveUserView(WikiGroupMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse("wiki_groups:admin-group", args=[kwargs["pk"]])
def get(self, request, *args, **kwargs):
user = User.objects.filter(pk=kwargs["user_pk"]).first()
if user is not None:
self.object.users.remove(user)
return super().get(request, *args, **kwargs)
class RemoveManagerView(WikiGroupMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse("wiki_groups:admin-group", args=[kwargs["pk"]])
def get(self, request, *args, **kwargs):
user = User.objects.filter(pk=kwargs["user_pk"]).first()
if user is not None:
self.object.users.remove(user)
self.object.managers.remove(user)
return super().get(request, *args, **kwargs)
class RemoveGroupView(WikiGroupMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse("wiki_groups:admin-group", args=[kwargs["pk"]])
def get(self, request, *args, **kwargs):
group = WikiGroup.objects.filter(pk=kwargs["group_pk"]).first()
if group is not None:
self.object.includes_groups.remove(group)
return super().get(request, *args, **kwargs)
class AddUserView(WikiGroupMixin, BaseFormView):
form_class = SelectUserForm
def get_success_url(self):
return reverse("wiki_groups:admin-group", args=[self.object.pk])
def form_valid(self, form):
self.object.users.add(form.cleaned_data["user"])
return super().form_valid(form)
def form_invalid(self, form):
self.request.session["wiki_form_errors"] = {
"user": {"value": form["user"].value(), "msg": form.errors["user"]}
}
return HttpResponseRedirect(self.get_success_url())
class AddManagerView(WikiGroupMixin, BaseFormView):
form_class = SelectUserForm
def get_success_url(self):
return reverse("wiki_groups:admin-group", args=[self.object.pk])
def form_valid(self, form):
self.object.managers.add(form.cleaned_data["user"])
return super().form_valid(form)
def form_invalid(self, form):
self.request.session["wiki_form_errors"] = {
"manager": {"value": form["user"].value(), "msg": form.errors["user"]}
}
return HttpResponseRedirect(self.get_success_url())
class AddGroupView(WikiGroupMixin, BaseFormView):
"""Adds an existing metagroup to this group"""
form_class = SelectGroupForm
def get_success_url(self):
return reverse("wiki_groups:admin-group", args=[self.object.pk])
def form_valid(self, form):
subgroup = form.cleaned_data["group"]
group = self.object
if group.group_in_cycle(list(group.includes_groups.all()) + [subgroup]):
form.add_error(
"group",
(
"Ajout impossible sous peine de créer un cycle "
f"({group} est inclus dans {subgroup})"
),
)
return self.form_invalid(form)
group.includes_groups.add(subgroup)
return super().form_valid(form)
def form_invalid(self, form):
self.request.session["wiki_form_errors"] = {
"group_add": {"value": form["group"].value(), "msg": form.errors["group"]}
}
return HttpResponseRedirect(self.get_success_url())
class CreateGroupView(WikiGroupMixin, BaseFormView):
"""Creates a metagroup included in this group"""
form_class = CreateGroupForm
def get_success_url(self):
return reverse("wiki_groups:admin-group", args=[self.object.pk])
def form_valid(self, form):
new_group = form.cleaned_data["group"]
self.object.includes_groups.add(new_group)
new_group.managers.add(self.request.user)
return super().form_valid(form)
def form_invalid(self, form):
self.request.session["wiki_form_errors"] = {
"group_create": {
"value": form["group"].value(),
"msg": form.errors["group"],
}
}
return HttpResponseRedirect(self.get_success_url())
class DeleteGroupView(SingleObjectMixin, StaffMixin, RedirectView):
model = WikiGroup
url = reverse_lazy("wiki_groups:managed-groups")
def get(self, request, *args, **kwargs):
group = self.get_object()
# On enlève les membres pour répercuter les changements
group.users.clear()
# On utilise la propagation de la suppression django_group -> wiki_group
group.django_group.delete()
return super().get(request, *args, **kwargs)