diff --git a/groupmanagement/admin.py b/groupmanagement/admin.py index 4d21a00f..aa7ea35a 100644 --- a/groupmanagement/admin.py +++ b/groupmanagement/admin.py @@ -1,13 +1,15 @@ from __future__ import unicode_literals from django.contrib import admin -from groupmanagement.models import GroupDescription from groupmanagement.models import GroupRequest -from groupmanagement.models import HiddenGroup -from groupmanagement.models import OpenGroup +from groupmanagement.models import AuthGroup -admin.site.register(GroupDescription) +class AuthGroupAdmin(admin.ModelAdmin): + """ + Admin model for AuthGroup + """ + filter_horizontal = ('group_leaders',) + admin.site.register(GroupRequest) -admin.site.register(HiddenGroup) -admin.site.register(OpenGroup) +admin.site.register(AuthGroup, AuthGroupAdmin) diff --git a/groupmanagement/managers.py b/groupmanagement/managers.py new file mode 100644 index 00000000..cb0d0486 --- /dev/null +++ b/groupmanagement/managers.py @@ -0,0 +1,20 @@ +from django.contrib.auth.models import Group +from django.conf import settings + +class GroupManager: + def __init__(self): + pass + + @staticmethod + def get_joinable_groups(): + return Group.objects.exclude(authgroup__internal=True) + + @staticmethod + def joinable_group(group): + """ + Check if a group is a user joinable group, i.e. + not an internal group for Corp, Alliance, Members etc + :param group: django.contrib.auth.models.Group object + :return: bool True if its joinable, False otherwise + """ + return not group.authgroup.internal diff --git a/groupmanagement/migrations/0004_authgroup.py b/groupmanagement/migrations/0004_authgroup.py new file mode 100644 index 00000000..f0cf52ac --- /dev/null +++ b/groupmanagement/migrations/0004_authgroup.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-12-04 10:25 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +from django.core.exceptions import ObjectDoesNotExist +import django.db.models.deletion + + +def internal_group(group): + return ( + "Corp_" in group.name or + "Alliance_" in group.name or + settings.DEFAULT_AUTH_GROUP in group.name or + settings.DEFAULT_BLUE_GROUP in group.name + ) + + +def combine_group_models(apps, schema_editor): + Group = apps.get_model("auth", "Group") + AuthGroup = apps.get_model("groupmanagement", "AuthGroup") + + for group in Group.objects.all(): + authgroup = AuthGroup(group=group) + if not hasattr(group, 'hiddengroup'): + authgroup.hidden = False + if hasattr(group, 'opengroup'): + authgroup.open = True + + if hasattr(group, 'groupdescription'): + authgroup.description = group.groupdescription.description + + authgroup.internal = internal_group(group) + authgroup.save() + + +def reverse_group_models(apps, schema_editor): + Group = apps.get_model("auth", "Group") + GroupDescription = apps.get_model("groupmanagement", "GroupDescription") + OpenGroup = apps.get_model("groupmanagement", "OpenGroup") + HiddenGroup = apps.get_model("groupmanagement", "HiddenGroup") + + for group in Group.objects.all(): + if not hasattr(group, 'authgroup') or group.authgroup is None: + continue + if group.authgroup.open: + OpenGroup.objects.get_or_create(group=group) + else: + try: + OpenGroup.objects.get(group=group).delete() + except ObjectDoesNotExist: + pass + + if group.authgroup.hidden: + HiddenGroup.objects.get_or_create(group=group) + else: + try: + HiddenGroup.objects.get(group=group).delete() + except ObjectDoesNotExist: + pass + + if len(group.authgroup.description): + GroupDescription.objects.update_or_create(group=group, + defaults={'description': group.authgroup.description}) + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0008_alter_user_username_max_length'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('groupmanagement', '0003_default_groups'), + ] + + operations = [ + migrations.CreateModel( + name='AuthGroup', + fields=[ + ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, + serialize=False, to='auth.Group')), + ('internal', models.BooleanField(default=True, help_text='Internal group, users cannot see, join or request to join this group.
Used for groups such as Members, Corp_*, Alliance_* etc.
Overrides Hidden and Open options when selected.')), + ('hidden', models.BooleanField(default=True, help_text='Group is hidden from users but can still join with the correct link.')), + ('open', models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request.
If the group is not open users will need their request manually approved.')), + ('description', models.CharField(max_length=512, help_text='Description of the group shown to users.', )), + + ('group_leaders', models.ManyToManyField(related_name='leads_groups', to=settings.AUTH_USER_MODEL, help_text='Group leaders can process group requests for this group specifically. Use the auth.group_management permission to allow a user to manage all groups.',)), + ], + ), + migrations.RunPython(combine_group_models, reverse_group_models), + migrations.RemoveField( + model_name='groupdescription', + name='group', + ), + migrations.RemoveField( + model_name='hiddengroup', + name='group', + ), + migrations.RemoveField( + model_name='opengroup', + name='group', + ), + migrations.DeleteModel( + name='GroupDescription', + ), + migrations.DeleteModel( + name='HiddenGroup', + ), + migrations.DeleteModel( + name='OpenGroup', + ), + ] diff --git a/groupmanagement/models.py b/groupmanagement/models.py index ffad61aa..adf0d2d2 100644 --- a/groupmanagement/models.py +++ b/groupmanagement/models.py @@ -3,19 +3,12 @@ from django.utils.encoding import python_2_unicode_compatible from django.db import models from django.contrib.auth.models import User from django.contrib.auth.models import Group +from django.db.models.signals import post_save +from django.dispatch import receiver from eveonline.models import EveCharacter -@python_2_unicode_compatible -class GroupDescription(models.Model): - description = models.CharField(max_length=512) - group = models.OneToOneField(Group) - - def __str__(self): - return self.group.name + " - Description" - - @python_2_unicode_compatible class GroupRequest(models.Model): status = models.CharField(max_length=254) @@ -29,16 +22,57 @@ class GroupRequest(models.Model): @python_2_unicode_compatible -class HiddenGroup(models.Model): - group = models.OneToOneField(Group) +class AuthGroup(models.Model): + """ + Extends Django Group model with a one-to-one field + Attributes are accessible via group as if they were in the model + e.g. group.authgroup.internal + + Logic: + Internal - not requestable by users, at all. Covers Corp_, Alliance_, Members etc groups. + Groups are internal by default + + Not Internal and: + Hidden - users cannot view, can request if they have the direct link. + Not Hidden - Users can view and request the group + Open - Users are automatically accepted into the group + Not Open - Users requests must be approved before they are added to the group + """ + group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True) + + internal = models.BooleanField(default=True, + help_text="Internal group, users cannot see, join or request to join this group.
" + "Used for groups such as Members, Corp_*, Alliance_* etc.
" + "Overrides Hidden and Open options when selected.") + hidden = models.BooleanField(default=True, + help_text="Group is hidden from users but can still join with the correct link.") + open = models.BooleanField(default=False, + help_text="Group is open and users will be automatically added upon request.
" + "If the group is not open users will need their request manually approved.") + # Group leaders have management access to this group + group_leaders = models.ManyToManyField(User, related_name='leads_groups', + help_text="Group leaders can process group requests for this group " + "specifically. Use the auth.group_management permission to allow " + "a user to manage all groups.") + + description = models.CharField(max_length=512, help_text="Description of the group shown to users.") def __str__(self): - return self.group.name + " - Hidden" + return self.group.name -@python_2_unicode_compatible -class OpenGroup(models.Model): - group = models.OneToOneField(Group) +@receiver(post_save, sender=Group) +def create_auth_group(sender, instance, created, **kwargs): + """ + Creates the AuthGroup model when a group is created + """ + if created: + AuthGroup.objects.create(group=instance) - def __str__(self): - return self.group.name + " - Open" + +@receiver(post_save, sender=Group) +def save_auth_group(sender, instance, **kwargs): + """ + Ensures AuthGroup model is saved automatically + """ + instance.authgroup.save() diff --git a/groupmanagement/views.py b/groupmanagement/views.py index e79ab0da..0e11cdb5 100755 --- a/groupmanagement/views.py +++ b/groupmanagement/views.py @@ -1,22 +1,18 @@ from __future__ import unicode_literals from django.shortcuts import render, redirect -from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import Group from django.contrib import messages from notifications import notify -from groupmanagement.models import GroupDescription +from groupmanagement.managers import GroupManager from groupmanagement.models import GroupRequest -from groupmanagement.models import HiddenGroup -from groupmanagement.models import OpenGroup from authentication.models import AuthServicesInfo from eveonline.managers import EveManager from django.utils.translation import ugettext_lazy as _ from django.db.models import Count from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.http import Http404 -from itertools import chain import logging @@ -48,8 +44,7 @@ def group_management(request): def group_membership(request): logger.debug("group_membership called by user %s" % request.user) # Get all open and closed groups - groups = [group for group in Group.objects.all().annotate(num_members=Count('user')).order_by('name') - if joinable_group(group)] + groups = Group.objects.exclude(authgroup__internal=True).annotate(num_members=Count('user')).order_by('name') render_items = {'groups': groups} @@ -64,7 +59,7 @@ def group_membership_list(request, group_id): group = Group.objects.get(id=group_id) # Check its a joinable group i.e. not corp or internal - if not joinable_group(group): + if not GroupManager.joinable_group(group): raise PermissionDenied except ObjectDoesNotExist: @@ -214,31 +209,16 @@ def group_leave_reject_request(request, group_request_id): @login_required def groups_view(request): logger.debug("groups_view called by user %s" % request.user) - paired_list = [] + groups = [] - for group in Group.objects.all(): - # Check if group is a corp - if not joinable_group(group): - pass - elif HiddenGroup.objects.filter(group=group).exists(): - pass - else: - # Get the descriptionn - group_desc = GroupDescription.objects.filter(group=group) + for group in GroupManager.get_joinable_groups(): + # Exclude hidden + if not group.authgroup.hidden: group_request = GroupRequest.objects.filter(user=request.user).filter(group=group) - if group_desc: - if group_request: - paired_list.append((group, group_desc[0], group_request[0])) - else: - paired_list.append((group, group_desc[0], "")) - else: - if group_request: - paired_list.append((group, "", group_request[0])) - else: - paired_list.append((group, "", "")) + groups.append({'group': group, 'request': group_request[0] if group_request else None}) - render_items = {'pairs': paired_list} + render_items = {'groups': groups} return render(request, 'registered/groups.html', context=render_items) @@ -246,12 +226,12 @@ def groups_view(request): def group_request_add(request, group_id): logger.debug("group_request_add called by user %s for group id %s" % (request.user, group_id)) group = Group.objects.get(id=group_id) - if not joinable_group(group): + if not GroupManager.joinable_group(group): logger.warning("User %s attempted to join group id %s but it is not a joinable group" % (request.user, group_id)) messages.warning(request, "You cannot join that group") return redirect('auth_groups') - if OpenGroup.objects.filter(group=group).exists(): + if group.authgroup.open: logger.info("%s joining %s as is an open group" % (request.user, group)) request.user.groups.add(group) return redirect("auth_groups") @@ -272,7 +252,7 @@ def group_request_add(request, group_id): def group_request_leave(request, group_id): logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id)) group = Group.objects.get(id=group_id) - if not joinable_group(group): + if not GroupManager.joinable_group(group): logger.warning("User %s attempted to leave group id %s but it is not a joinable group" % (request.user, group_id)) messages.warning(request, "You cannot leave that group") @@ -282,7 +262,7 @@ def group_request_leave(request, group_id): (request.user, group_id)) messages.warning(request, "You are not a member of that group") return redirect('auth_groups') - if OpenGroup.objects.filter(group=group).exists(): + if group.authgroup.open: logger.info("%s leaving %s as is an open group" % (request.user, group)) request.user.groups.remove(group) return redirect("auth_groups") @@ -297,18 +277,3 @@ def group_request_leave(request, group_id): logger.info("Created group leave request for user %s to group %s" % (request.user, Group.objects.get(id=group_id))) messages.success(request, 'Applied to leave group %s.' % group) return redirect("auth_groups") - - -def joinable_group(group): - """ - Check if a group is a user joinable group, i.e. - not an internal group for Corp, Alliance, Members etc - :param group: django.contrib.auth.models.Group object - :return: bool True if its joinable, False otherwise - """ - return ( - "Corp_" not in group.name and - "Alliance_" not in group.name and - settings.DEFAULT_AUTH_GROUP not in group.name and - settings.DEFAULT_BLUE_GROUP not in group.name - ) diff --git a/stock/templates/registered/groupmembership.html b/stock/templates/registered/groupmembership.html index af78e895..173b152e 100644 --- a/stock/templates/registered/groupmembership.html +++ b/stock/templates/registered/groupmembership.html @@ -24,11 +24,11 @@ {% for group in groups %} {{ group.name }} - {{ group.groupdescription.description }} + {{ group.authgroup.description }} - {% if group.hiddengroup %} + {% if group.authgroup.hidden %} {% trans "Hidden" %} - {% elif group.opengroup %} + {% elif group.authgroup.open %} {% trans "Open" %} {% else %} {% trans "Requestable" %} diff --git a/stock/templates/registered/groups.html b/stock/templates/registered/groups.html index 2581544a..21c5ec6a 100644 --- a/stock/templates/registered/groups.html +++ b/stock/templates/registered/groups.html @@ -11,36 +11,36 @@

{% trans "Available Groups" %}

{% if STATE == MEMBER_STATE or user.is_superuser %} - {% if pairs %} + {% if groups %} - - + + - {% for pair in pairs %} + {% for g in groups %} - - + +
{% trans "GroupName" %}{% trans "GroupDesc" %}{% trans "Name" %}{% trans "Description" %} {% trans "Action" %}
{{ pair.0.name }}{{ pair.1.description }}{{ g.group.name }}{{ g.group.authgroup.description }} - {% if pair.0 in user.groups.all %} - {% if pair.2 == "" %} - + {% if g.group in user.groups.all %} + {% if not g.request %} + {% trans "Leave" %} {% else %} {% endif %} - {% elif pair.2 == "" %} - + {% elif not g.request %} + {% trans "Request" %} {% else %} {% endif %}