diff --git a/shared/templates/wiki/base.html b/shared/templates/wiki/base.html index 4188a8c..ff5d311 100644 --- a/shared/templates/wiki/base.html +++ b/shared/templates/wiki/base.html @@ -6,50 +6,57 @@ {% block wiki_header_branding %} - Wiki ENS + Wiki ENS {% endblock %} {% block wiki_header_navlinks %}
{% endblock %} diff --git a/wiki_groups/forms.py b/wiki_groups/forms.py new file mode 100644 index 0000000..8c397fc --- /dev/null +++ b/wiki_groups/forms.py @@ -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 diff --git a/wiki_groups/migrations/0002_wikigroup_managers.py b/wiki_groups/migrations/0002_wikigroup_managers.py new file mode 100644 index 0000000..8ae5dd3 --- /dev/null +++ b/wiki_groups/migrations/0002_wikigroup_managers.py @@ -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), + ), + ] diff --git a/wiki_groups/models.py b/wiki_groups/models.py index 038f303..55ae155 100644 --- a/wiki_groups/models.py +++ b/wiki_groups/models.py @@ -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. diff --git a/wiki_groups/templates/wiki_groups/admin.html b/wiki_groups/templates/wiki_groups/admin.html new file mode 100644 index 0000000..590fad7 --- /dev/null +++ b/wiki_groups/templates/wiki_groups/admin.html @@ -0,0 +1,207 @@ +{% extends "wiki/base.html" %} +{% load sekizai_tags %} + +{% block wiki_site_title %}Groupes administrés - WikiENS{% endblock %} + +{% block wiki_contents %} +Attention, cette action est irréversible.
+