diff --git a/groupadmin/migrations/0002_groupproperties_owners_manage_owners.py b/groupadmin/migrations/0002_groupproperties_owners_manage_owners.py new file mode 100644 index 0000000..108df1c --- /dev/null +++ b/groupadmin/migrations/0002_groupproperties_owners_manage_owners.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2023-11-25 20:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("groupadmin", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="groupproperties", + name="owners_manage_owners", + field=models.BooleanField(default=False), + ), + ] diff --git a/groupadmin/models.py b/groupadmin/models.py index 45b821f..9c9b67d 100644 --- a/groupadmin/models.py +++ b/groupadmin/models.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Tim Hawes +# SPDX-FileCopyrightText: 2023 Tim Hawes # # SPDX-License-Identifier: MIT @@ -26,6 +26,7 @@ class GroupProperties(models.Model): ) description = models.CharField(max_length=255, blank=True, null=True) self_service = models.BooleanField(default=False) + owners_manage_owners = models.BooleanField(default=False) def __str__(self): return self.group.name diff --git a/groupadmin/urls.py b/groupadmin/urls.py index f343925..aabc2fa 100644 --- a/groupadmin/urls.py +++ b/groupadmin/urls.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Tim Hawes +# SPDX-FileCopyrightText: 2023 Tim Hawes # # SPDX-License-Identifier: MIT @@ -9,11 +9,25 @@ urlpatterns = [ path("", views.groupadmin_list, name="groupadmin_list"), path("", views.groupadmin_view, name="groupadmin_view"), - path("/add", views.groupadmin_add_user, name="groupadmin_add_user"), path( - "/remove/", - views.groupadmin_remove_user, - name="groupadmin_remove_user", + "/members/add", + views.groupadmin_add_member, + name="groupadmin_add_member", + ), + path( + "/members/remove/", + views.groupadmin_remove_member, + name="groupadmin_remove_member", + ), + path( + "/owners/add", + views.groupadmin_add_owner, + name="groupadmin_add_owner", + ), + path( + "/owners/remove/", + views.groupadmin_remove_owner, + name="groupadmin_remove_owner", ), path("/join", views.groupadmin_join, name="groupadmin_join"), path("/leave", views.groupadmin_leave, name="groupadmin_leave"), diff --git a/groupadmin/views.py b/groupadmin/views.py index 664a789..d0bf079 100644 --- a/groupadmin/views.py +++ b/groupadmin/views.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Tim Hawes +# SPDX-FileCopyrightText: 2023 Tim Hawes # # SPDX-License-Identifier: MIT @@ -6,11 +6,13 @@ from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponseForbidden from django.shortcuts import render from django.urls import reverse from django.views.decorators.http import require_POST +from .models import GroupOwnership + @login_required def groupadmin_list(request): @@ -40,26 +42,43 @@ def groupadmin_list(request): def groupadmin_view(request, group_name): group = request.user.groupownerships.get(group__name=group_name).group - new_users = {} + new_members = {} for user in get_user_model().objects.filter(is_active=True): - new_users[user.username] = user.id + new_members[user.username] = user.id + new_owners = new_members.copy() - users = {} + members = {} for user in group.user_set.all(): try: - del new_users[user.username] + del new_members[user.username] + except KeyError: + pass + members[user.username] = { + "id": user.id, + "username": user.username, + } + + owners = {} + for groupownership in group.owners.all(): + user = groupownership.user + try: + del new_owners[user.username] except KeyError: pass - users[user.username] = { + owners[user.username] = { "id": user.id, "username": user.username, } context = { "group": group, - "users": [users[username] for username in sorted(users.keys())], - "new_users": [ - (new_users[username], username) for username in sorted(new_users.keys()) + "members": [members[username] for username in sorted(members.keys())], + "owners": [owners[username] for username in sorted(owners.keys())], + "new_members": [ + (new_members[username], username) for username in sorted(new_members.keys()) + ], + "new_owners": [ + (new_owners[username], username) for username in sorted(new_owners.keys()) ], } @@ -68,21 +87,50 @@ def groupadmin_view(request, group_name): @login_required @require_POST -def groupadmin_add_user(request, group_name): +def groupadmin_add_member(request, group_name): group = request.user.groupownerships.get(group__name=group_name).group user = get_user_model().objects.get(id=int(request.POST["user_id"])) group.user_set.add(user) - messages.add_message(request, messages.SUCCESS, f"User {user.username} added.") + messages.add_message(request, messages.SUCCESS, f"Member {user.username} added.") return HttpResponseRedirect(reverse("groupadmin_view", args=[group_name])) @login_required @require_POST -def groupadmin_remove_user(request, group_name, user_id): +def groupadmin_remove_member(request, group_name, user_id): group = request.user.groupownerships.get(group__name=group_name).group user = get_user_model().objects.get(id=user_id) group.user_set.remove(user) - messages.add_message(request, messages.SUCCESS, f"User {user.username} removed.") + messages.add_message(request, messages.SUCCESS, f"Member {user.username} removed.") + return HttpResponseRedirect(reverse("groupadmin_view", args=[group_name])) + + +@login_required +@require_POST +def groupadmin_add_owner(request, group_name): + group = request.user.groupownerships.get(group__name=group_name).group + user = get_user_model().objects.get(id=int(request.POST["user_id"])) + if request.user == user: + # this condition should never match, added just-in-case + return HttpResponseForbidden("Cannot add self") + if not group.properties.owners_manage_owners: + return HttpResponseForbidden("Cannot manage owners") + GroupOwnership.objects.create(group=group, user=user) + messages.add_message(request, messages.SUCCESS, f"Owner {user.username} added.") + return HttpResponseRedirect(reverse("groupadmin_view", args=[group_name])) + + +@login_required +@require_POST +def groupadmin_remove_owner(request, group_name, user_id): + group = request.user.groupownerships.get(group__name=group_name).group + user = get_user_model().objects.get(id=user_id) + if request.user == user: + return HttpResponseForbidden("Cannot remove self") + if not group.properties.owners_manage_owners: + return HttpResponseForbidden("Cannot manage owners") + GroupOwnership.objects.get(group=group, user=user).delete() + messages.add_message(request, messages.SUCCESS, f"Owner {user.username} removed.") return HttpResponseRedirect(reverse("groupadmin_view", args=[group_name])) diff --git a/hackdb/templates/groupadmin/group_view.html b/hackdb/templates/groupadmin/group_view.html index 91dd6a4..49e1e55 100644 --- a/hackdb/templates/groupadmin/group_view.html +++ b/hackdb/templates/groupadmin/group_view.html @@ -14,11 +14,11 @@

Group {{ group.name }}

- {% for user in users %} + {% for user in members %} {{ user.username }} -
+ {% csrf_token %} +

+ {% if group.properties.owners_manage_owners %} +
+ + + + + + + + + {% for user in owners %} + + + + + {% endfor %} + +
OwnerDelete
{{ user.username }} + {% if user.username != request.user.username %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+
+

+

+ {% csrf_token %} + + +
+

+
+ {% endif %}