add group administration

This commit is contained in:
Jens Langhammer 2019-03-08 15:49:45 +01:00
parent ca663d16fc
commit 2d7e8f1b50
9 changed files with 195 additions and 118 deletions

View File

@ -5,35 +5,44 @@
{% block nav_secondary %} {% block nav_secondary %}
<ul class="nav navbar-nav navbar-persistent"> <ul class="nav navbar-nav navbar-persistent">
<li class="{% is_active 'passbook_admin:overview' %}"> <li class="{% is_active 'passbook_admin:overview' %}">
<a href="{% url 'passbook_admin:overview' %}">{% trans 'Overview' %}</a> <a href="{% url 'passbook_admin:overview' %}">{% trans 'Overview' %}</a>
</li> </li>
<li class="{% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}"> <li
<a href="{% url 'passbook_admin:applications' %}">{% trans 'Applications' %}</a> class="{% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
</li> <a href="{% url 'passbook_admin:applications' %}">{% trans 'Applications' %}</a>
<li class="{% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}"> </li>
<a href="{% url 'passbook_admin:sources' %}">{% trans 'Sources' %}</a> <li
</li> class="{% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
<li class="{% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}"> <a href="{% url 'passbook_admin:sources' %}">{% trans 'Sources' %}</a>
<a href="{% url 'passbook_admin:providers' %}">{% trans 'Providers' %}</a> </li>
</li> <li
<li class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> class="{% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
<a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a> <a href="{% url 'passbook_admin:providers' %}">{% trans 'Providers' %}</a>
</li> </li>
<li class="{% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}"> <li
<a href="{% url 'passbook_admin:policies' %}">{% trans 'Policies' %}</a> class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
</li> <a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a>
<li class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> </li>
<a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a> <li
</li> class="{% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
<li class="{% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}"> <a href="{% url 'passbook_admin:policies' %}">{% trans 'Policies' %}</a>
<a href="{% url 'passbook_admin:users' %}">{% trans 'Users' %}</a> </li>
</li> <li
<li class="{% is_active 'passbook_admin:audit-log' %}"> class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
<a href="{% url 'passbook_admin:audit-log' %}">{% trans 'Audit Log' %}</a> <a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a>
</li> </li>
<li class="{% is_active_app 'admin' %}"> <li class="{% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
<a href="{% url 'admin:index' %}">{% trans 'Django' %}</a> <a href="{% url 'passbook_admin:users' %}">{% trans 'Users' %}</a>
</li> </li>
<li class="{% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
<a href="{% url 'passbook_admin:groups' %}">{% trans 'Groups' %}</a>
</li>
<li class="{% is_active 'passbook_admin:audit-log' %}">
<a href="{% url 'passbook_admin:audit-log' %}">{% trans 'Audit Log' %}</a>
</li>
<li class="{% is_active_app 'admin' %}">
<a href="{% url 'admin:index' %}">{% trans 'Django' %}</a>
</li>
</ul> </ul>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,45 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load utils %}
{% block title %}
{% title %}
{% endblock %}
{% block content %}
<div class="container">
<h1><span class="pficon-users"></span> {% trans "Groups" %}</h1>
<span>{% trans "Group users together and give them permissions based on the membership." %}</span>
<hr>
<a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Parent' %}</th>
<th>{% trans 'Members' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for group in object_list %}
<tr>
<td>{{ group.name }}</td>
<td>{{ group.parent }}</td>
<td>{{ group.user_set.all|length }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:group-update' pk=group.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:group-delete' pk=group.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -1,83 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load static %}
{% load utils %}
{% block head %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-treeview.min.css'%}">
{% endblock %}
{% block scripts %}
{{ block.super }}
<script src="{% static 'js/bootstrap-treeview.min.js' %}"></script>
<script>
var cleanupData = function (obj) {
return {
text: obj.name,
href: '?group=' + obj.uuid,
nodes: obj.children.map(cleanupData),
};
}
$(function() {
var apiUrl = "{% url 'passbook_admin:group-list' %}?format=json";
$.ajax({
url: apiUrl,
}).done(function(data) {
$('#treeview1').treeview({
collapseIcon: "fa fa-angle-down",
data: data.map(cleanupData),
expandIcon: "fa fa-angle-right",
nodeIcon: "fa pficon-users",
showBorder: true,
enableLinks: true,
onNodeSelected: function (event, node) {
window.location.href = node.href;
}
});
});
});
</script>
{% endblock %}
{% block title %}
{% title %}
{% endblock %}
{% block content %}
<div class="col-md-3">
<div id="treeview1" class="treeview">
</div>
</div>
<div class="col-md-9">
<h1>{% trans "Invitations" %}</h1>
<a href="{% url 'passbook_admin:invitation-create' %}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Expiry' %}</th>
<th>{% trans 'Link' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for invitation in object_list %}
<tr>
<td>{{ invitation.expires|default:"Never" }}</td>
<td>
<pre>{{ invitation.link }}</pre>
</td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:invitation-delete' pk=invitation.uuid %}?back={{ request.get_full_path }}">{%
trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -58,6 +58,11 @@ urlpatterns = [
users.UserDeleteView.as_view(), name='user-delete'), users.UserDeleteView.as_view(), name='user-delete'),
path('users/<int:pk>/reset/', path('users/<int:pk>/reset/',
users.UserPasswordResetView.as_view(), name='user-password-reset'), users.UserPasswordResetView.as_view(), name='user-password-reset'),
# Groups
path('group/', groups.GroupListView.as_view(), name='group'),
path('group/create/', groups.GroupCreateView.as_view(), name='group-create'),
path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'),
path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
# Audit Log # Audit Log
path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'), path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'),
# Groups # Groups

View File

@ -1,12 +1,57 @@
"""passbook Group administration""" """passbook Group administration"""
from django.views.generic import ListView from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.forms.groups import GroupForm
from passbook.core.models import Group from passbook.core.models import Group
class GroupListView(AdminRequiredMixin, ListView): class GroupListView(AdminRequiredMixin, ListView):
"""Show list of all invitations""" """Show list of all groups"""
model = Group model = Group
template_name = 'administration/groups/list.html' ordering = 'name'
template_name = 'administration/group/list.html'
class GroupCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
"""Create new Group"""
form_class = GroupForm
template_name = 'generic/create.html'
success_url = reverse_lazy('passbook_admin:groups')
success_message = _('Successfully created Group')
def get_context_data(self, **kwargs):
kwargs['type'] = 'Group'
return super().get_context_data(**kwargs)
class GroupUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update group"""
model = Group
form_class = GroupForm
template_name = 'generic/update.html'
success_url = reverse_lazy('passbook_admin:groups')
success_message = _('Successfully updated Group')
class GroupDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete group"""
model = Group
template_name = 'generic/delete.html'
success_url = reverse_lazy('passbook_admin:groups')
success_message = _('Successfully deleted Group')
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)

View File

@ -0,0 +1,30 @@
"""passbook Core Group forms"""
from django import forms
from passbook.core.models import Group, User
class GroupForm(forms.ModelForm):
"""Group Form"""
members = forms.ModelMultipleChoiceField(User.objects.all(), required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.initial['members'] = self.instance.user_set.values_list('pk', flat=True)
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
if instance.pk:
instance.user_set.clear()
instance.user_set.add(*self.cleaned_data['users'])
return instance
class Meta:
model = Group
fields = ['name', 'parent', 'members', 'tags']
widgets = {
'name': forms.TextInput(),
}

View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-03-08 14:17
import django.contrib.postgres.fields.hstore
from django.contrib.postgres.operations import HStoreExtension
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0016_auto_20190227_1355'),
]
operations = [
migrations.RemoveField(
model_name='group',
name='extra_data',
),
HStoreExtension(),
migrations.AddField(
model_name='group',
name='tags',
field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
),
]

View File

@ -8,7 +8,7 @@ from typing import Tuple, Union
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField, HStoreField
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.timezone import now from django.utils.timezone import now
@ -31,7 +31,7 @@ class Group(UUIDModel):
name = models.CharField(_('name'), max_length=80) name = models.CharField(_('name'), max_length=80)
parent = models.ForeignKey('Group', blank=True, null=True, parent = models.ForeignKey('Group', blank=True, null=True,
on_delete=models.SET_NULL, related_name='children') on_delete=models.SET_NULL, related_name='children')
extra_data = models.TextField(blank=True) tags = HStoreField(default=dict)
def __str__(self): def __str__(self):
return "Group %s" % self.name return "Group %s" % self.name

View File

@ -60,6 +60,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.postgres',
'rest_framework', 'rest_framework',
'drf_yasg', 'drf_yasg',
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',