diff --git a/allianceauth/groupmanagement/managers.py b/allianceauth/groupmanagement/managers.py
index bdb6dc9d..0a6ebe24 100644
--- a/allianceauth/groupmanagement/managers.py
+++ b/allianceauth/groupmanagement/managers.py
@@ -23,6 +23,15 @@ class GroupManager:
"""
return not group.authgroup.internal
+ @staticmethod
+ def check_internal_group(group):
+ """
+ Check if a group is auditable, i.e not an internal group
+ :param group: django.contrib.auth.models.Group object
+ :return: bool True if it is auditable, false otherwise
+ """
+ return not group.authgroup.internal
+
@staticmethod
def has_management_permission(user):
return user.has_perm('auth.group_management')
diff --git a/allianceauth/groupmanagement/migrations/0009_requestlog.py b/allianceauth/groupmanagement/migrations/0009_requestlog.py
new file mode 100644
index 00000000..aaff4927
--- /dev/null
+++ b/allianceauth/groupmanagement/migrations/0009_requestlog.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.0.6 on 2018-06-04 02:45
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0008_alter_user_username_max_length'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('groupmanagement', '0008_remove_authgroup_permissions'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RequestLog',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('request_type', models.NullBooleanField(default=0)),
+ ('request_info', models.CharField(max_length=254)),
+ ('action', models.BooleanField(default=0)),
+ ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
+ ('request_actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/allianceauth/groupmanagement/models.py b/allianceauth/groupmanagement/models.py
index 3e389e1c..4e93a090 100644
--- a/allianceauth/groupmanagement/models.py
+++ b/allianceauth/groupmanagement/models.py
@@ -23,6 +23,37 @@ class GroupRequest(models.Model):
return self.user.username + ":" + self.group.name
+class RequestLog(models.Model):
+ request_type = models.NullBooleanField(default=0)
+ group = models.ForeignKey(Group, on_delete=models.CASCADE)
+ request_info = models.CharField(max_length=254)
+ action = models.BooleanField(default=0)
+ request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
+
+ def requestor(self):
+ return self.request_info.split(":")[0]
+
+ def type_to_str(self):
+ if self.request_type is None:
+ return "Removed"
+ elif self.request_type is True:
+ return "Leave"
+ elif self.request_type is False:
+ return "Join"
+
+ def action_to_str(self):
+ if self.action is True:
+ return "Accept"
+ elif self.action is False:
+ return "Reject"
+
+ def req_char(self):
+ usr = self.requestor()
+ user = User.objects.get(username=usr)
+ return user.profile.main_character
+
+
+
class AuthGroup(models.Model):
"""
Extends Django Group model with a one-to-one field
diff --git a/allianceauth/groupmanagement/templates/groupmanagement/audit.html b/allianceauth/groupmanagement/templates/groupmanagement/audit.html
new file mode 100644
index 00000000..c5f26892
--- /dev/null
+++ b/allianceauth/groupmanagement/templates/groupmanagement/audit.html
@@ -0,0 +1,40 @@
+{% extends "allianceauth/base.html" %}
+{% load staticfiles %}
+{% load i18n %}
+
+{% block page_title %}{{ group }} {% trans "Audit Log" %}{% endblock page_title %}
+{% block extra_css %}{% endblock extra_css %}
+
+{% block content %}
+
+
+ {% include 'groupmanagement/menu.html' %}
+
+ {% if entries %}
+
{{ group }} Audit Log
+
+
+ {% trans "Requestor" %} |
+ {% trans "Main Character" %} |
+ {% trans "Group" %} |
+ {% trans "Type" %} |
+ {% trans "Action" %} |
+ {% trans "Actor" %} |
+
+ {% for entry in entries %}
+
+ {{ entry.requestor }} |
+ {{ entry.req_char }} |
+ {{ entry.group }} |
+ {{ entry.type_to_str }} |
+ {{ entry.action_to_str }} |
+ {{ entry.request_actor }} |
+
+ {% endfor %}
+
+ {% else %}
+
{% trans "No entries found." %}
+ {% endif %}
+
+
+{% endblock content %}
diff --git a/allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html b/allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html
index 7384c23a..5e47a0be 100644
--- a/allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html
+++ b/allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html
@@ -41,6 +41,9 @@
title="{% trans "View Members" %}">
+
+
+
{% endfor %}
diff --git a/allianceauth/groupmanagement/urls.py b/allianceauth/groupmanagement/urls.py
index 37de11fe..3533df01 100644
--- a/allianceauth/groupmanagement/urls.py
+++ b/allianceauth/groupmanagement/urls.py
@@ -12,6 +12,7 @@ urlpatterns = [
name='membership'),
url(r'^membership/(\w+)/$', views.group_membership_list,
name='membership_list'),
+ url(r'^membership/(\w+)/audit/', views.group_membership_audit, name="audit_log"),
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
name='membership_remove'),
url(r'^request_add/(\w+)', views.group_request_add,
diff --git a/allianceauth/groupmanagement/views.py b/allianceauth/groupmanagement/views.py
index ee564a2d..39014f20 100755
--- a/allianceauth/groupmanagement/views.py
+++ b/allianceauth/groupmanagement/views.py
@@ -10,7 +10,7 @@ from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _
from .managers import GroupManager
-from .models import GroupRequest
+from .models import GroupRequest, RequestLog
from allianceauth.notifications import notify
@@ -65,6 +65,32 @@ def group_membership(request):
return render(request, 'groupmanagement/groupmembership.html', context=render_items)
+@login_required
+@user_passes_test(GroupManager.can_manage_groups)
+def group_membership_audit(request, group_id):
+ logger.debug("group_management_audit called by user %s" % request.user)
+ group = get_object_or_404(Group, id=group_id)
+ try:
+
+ # Check its a joinable group i.e. not corp or internal
+ # And the user has permission to manage it
+ if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
+ logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
+ (request.user, group_id))
+ raise PermissionDenied
+
+ except ObjectDoesNotExist:
+ raise Http404("Group does not exist")
+
+ entries = RequestLog.objects.filter(group=group)
+
+ render_items = {'entries': entries, 'group': group.name}
+
+ return render(request, 'groupmanagement/audit.html', context=render_items)
+
+
+
+
@login_required
@user_passes_test(GroupManager.can_manage_groups)
def group_membership_list(request, group_id):
@@ -112,6 +138,9 @@ def group_membership_remove(request, group_id, user_id):
try:
user = group.user_set.get(id=user_id)
+ request_info = user.username + ":" + group.name
+ log = RequestLog(request_type=None,group=group,request_info=request_info,action=1,request_actor=request.user)
+ log.save()
# Remove group from user
user.groups.remove(group)
logger.info("User %s removed user %s from group %s" % (request.user, user, group))
@@ -139,6 +168,8 @@ def group_accept_request(request, group_request_id):
group_request.user.groups.add(group)
group_request.user.save()
+ log = RequestLog(request_type=group_request.leave_request,group=group,request_info=group_request.__str__(),action=1,request_actor=request.user)
+ log.save()
group_request.delete()
logger.info("User %s accepted group request from user %s to group %s" % (
request.user, group_request.user, group_request.group.name))
@@ -172,6 +203,8 @@ def group_reject_request(request, group_request_id):
if group_request:
logger.info("User %s rejected group request from user %s to group %s" % (
request.user, group_request.user, group_request.group.name))
+ log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
+ log.save()
group_request.delete()
notify(group_request.user, "Group Application Rejected", level="danger",
message="Your application to %s has been rejected." % group_request.group)
@@ -204,6 +237,8 @@ def group_leave_accept_request(request, group_request_id):
group, created = Group.objects.get_or_create(name=group_request.group.name)
group_request.user.groups.remove(group)
group_request.user.save()
+ log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=1,request_actor=request.user)
+ log.save()
group_request.delete()
logger.info("User %s accepted group leave request from user %s to group %s" % (
request.user, group_request.user, group_request.group.name))
@@ -236,6 +271,8 @@ def group_leave_reject_request(request, group_request_id):
raise PermissionDenied
if group_request:
+ log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
+ log.save()
group_request.delete()
logger.info("User %s rejected group leave request from user %s for group %s" % (
request.user, group_request.user, group_request.group.name))