Merge branch 'master' into prod

This commit is contained in:
Martin Pépin 2021-10-26 22:59:23 +02:00
commit 8881cb2bf4
No known key found for this signature in database
GPG key ID: E7520278B1774448
10 changed files with 684 additions and 98 deletions

View file

@ -11,49 +11,49 @@
{% endblock %} {% endblock %}
{% block wiki_header_navlinks %} {% block wiki_header_navlinks %}
<ul class="nav navbar-nav"> <ul class="navbar-nav mr-auto">
<li class="active"><a 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> </ul>
<div class="navbar-right"> <div class="navbar-right">
<ul class="nav navbar-nav"> <ul class="navbar-nav">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="dropdown"> <li class="nav-item dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" class="dropdown-toggle" data-toggle="dropdown">
{% trans "Paramètres de compte" %} {% trans "Paramètres de compte" %}
<b class="caret"></b>
</a> </a>
<ul class="dropdown-menu"> <div class="dropdown-menu">
<li> <a class="dropdown-item" href="{% url 'account_email' %}">
<a href="{% url "account_email" %}"> <i class="fa fa-envelope"></i>
<i class="fa fa-envelope"></i> {% trans "Email" %}
{% trans "Email" %} </a>
</a> <a class="dropdown-item" href="{% url 'account_change_password' %}">
</li> <i class="fa fa-lock"></i>
<li> {% trans "Mot de passe" %}
<a href="{% url "account_change_password" %}"> </a>
<i class="fa fa-lock"></i> <a class="dropdown-item" href="{% url 'socialaccount_connections' %}" title="Clipper…">
{% trans "Mot de passe" %} <i class="fa fa-sign-in-alt"></i>
</a> {% trans "Connexions par tiers" %}
</li> </a>
<li> {% if request.user.is_staff or request.user.managed_groups.exists %}
<a href="{% url "socialaccount_connections" %}" title="Clipper…"> <div class="dropdown-divider"></div>
<i class="fa fa-sign-in"></i> <a class="dropdown-item" href="{% url 'wiki_groups:managed-groups' %}">
{% trans "Connexions par tiers" %} <i class="fa fa-cog"></i>
</a> {% trans "Liste des groupes gérés" %}
</li> </a>
<li class="divider"></span> {% endif %}
<li> <div class="dropdown-divider"></div>
<a href="{% url "account_logout" %}"> <a class="dropdown-item" href="{% url 'account_logout' %}">
<i class="fa fa-power-off"></i> <i class="fa fa-power-off"></i>
{% trans "Déconnexion" %} {% trans "Déconnexion" %}
</a> </a>
</li> </div>
</ul>
</li> </li>
{% else %} {% else %}
<li> <li class="nav-item">
<a href="{% url "account_signup" %}">{% trans "Sign Up" %}</a> <a class="nav-link" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -62,5 +62,5 @@
{% block wiki_footer_logo %} {% block wiki_footer_logo %}
<a href="http://www.eleves.ens.fr" class="pull-right"><img height="100px" src="{% static 'img/logoEleves.png' %}" /></a> <a href="http://www.eleves.ens.fr" class="pull-right"><img height="100px" src="{% static 'img/logoEleves.png' %}" /></a>
<p>Wiki maintenu par les élèves de l'École normale supérieure. <br /> En cas de pépin contacter <tt>cof-geek [chez] ens [point] fr</tt></p> <p>Wiki maintenu par les élèves de l'École normale supérieure. <br /> En cas de pépin contacter <tt>klub-dev [chez] ens [point] fr</tt></p>
{% endblock %} {% 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.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 from django.dispatch import receiver
User = get_user_model()
class WikiGroup(models.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 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. both users and other groups, allowing a DAG group structure.
@ -20,8 +22,8 @@ class WikiGroup(models.Model):
""" """
class CyclicStructureException(Exception): class CyclicStructureException(Exception):
""" Exception raised when a new edge introduces a cycle in the groups' """Exception raised when a new edge introduces a cycle in the groups'
structure """ structure"""
def __init__(self, from_group, to_group): def __init__(self, from_group, to_group):
self.from_group = from_group self.from_group = from_group
@ -40,22 +42,54 @@ class WikiGroup(models.Model):
related_name="included_in_groups", related_name="included_in_groups",
blank=True, 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): def __str__(self):
return str(self.django_group) return str(self.django_group)
def get_all_users(self): def get_all_users(self):
""" Get the queryset of all the users in this group, including recursively """Get the queryset of all the users in this group, including recursively
included users """ included users"""
users_set = self.users.all() users_set = self.users.all()
for subgroup in self.includes_groups.all(): for subgroup in self.includes_groups.all():
users_set = users_set.union(subgroup.get_all_users()) users_set = users_set.union(subgroup.get_all_users())
return users_set 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): def propagate_update(self, already_notified=None):
""" Commits itself to the Django group, and calls this method on every group in """Commits itself to the Django group, and calls this method on every group in
`included_in_groups` """ `included_in_groups`"""
# Check that we did not already propagate the update signal to this group # Check that we did not already propagate the update signal to this group
if already_notified is None: if already_notified is None:
@ -69,20 +103,20 @@ class WikiGroup(models.Model):
metagroup.propagate_update(already_notified=already_notified) metagroup.propagate_update(already_notified=already_notified)
def commit_to_django_group(self): 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()) self.django_group.user_set.set(self.get_all_users())
def group_in_cycle(self, with_children): 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 `with_children` as child nodes. This assumes that the graph currently stored is
acyclic. acyclic.
Returns `None` if no cycle is found, else retuns a child from `with_children` 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): 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: if cur_node.pk in visited_nodes:
return False return False
if cur_node.pk == self.pk: 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") @receiver(post_save, sender=WikiGroup, dispatch_uid="on_wiki_group_changed")
def on_wiki_group_changed(sender, instance, **kwargs): 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() instance.propagate_update()
@ -113,8 +147,8 @@ def on_wiki_group_changed(sender, instance, **kwargs):
dispatch_uid="on_wiki_group_includes_changed", dispatch_uid="on_wiki_group_includes_changed",
) )
def on_wiki_group_includes_changed(sender, instance, action, **kwargs): def on_wiki_group_includes_changed(sender, instance, action, **kwargs):
""" Commit the related WikiGroups to Django Group upon change of the set of """Commit the related WikiGroups to Django Group upon change of the set of
included other groups """ included other groups"""
if action in ["post_add", "post_remove", "post_clear"]: if action in ["post_add", "post_remove", "post_clear"]:
instance.propagate_update() instance.propagate_update()
@ -125,7 +159,7 @@ def on_wiki_group_includes_changed(sender, instance, action, **kwargs):
dispatch_uid="on_wiki_group_users_changed", dispatch_uid="on_wiki_group_users_changed",
) )
def on_wiki_group_users_changed(sender, instance, action, **kwargs): 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"]: if action in ["post_add", "post_remove", "post_clear"]:
instance.propagate_update() 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", dispatch_uid="on_wiki_group_includes_check_acyclic",
) )
def on_wiki_group_includes_check_acyclic(sender, instance, action, pk_set, **kwargs): 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 PLEASE NOTE that this check is only a fallback, and that forms should validate
the acyclicity before committing anything. 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 staticfiles %}
{% 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"></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 django.urls import path
from wiki_groups import views
from wiki_groups import views
app_name = "wiki_groups" app_name = "wiki_groups"
urlpatterns = [ urlpatterns = [
path("", views.GroupList.as_view(), name="list"),
path("dot_graph", views.dot_graph, name="dot_graph"), 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.contrib.auth import get_user_model
from django.views.generic import TemplateView 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 from wiki_groups.models import WikiGroup
User = get_user_model()
def dot_graph(request): def dot_graph(request):
response = HttpResponse(content_type="text/vnd.graphviz") response = HttpResponse(content_type="text/vnd.graphviz")
@ -22,4 +30,214 @@ def dot_graph(request):
return response 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)