#1 Revived users app which can now actually grant roles

This commit is contained in:
Sebastian Henschel 2016-11-19 12:18:59 +01:00
parent c253f8f730
commit de58db5c84
12 changed files with 251 additions and 203 deletions

View File

@ -47,7 +47,7 @@ INSTALLED_APPS = [
'base',
'oauth',
'consumers',
# 'users',
'users',
# 'api_calls',
# 'api_config',
]

View File

@ -22,7 +22,7 @@ urlpatterns = [
url(r'^$', HomeView.as_view(), name="home"),
url(r'^oauth/', include('oauth.urls')),
url(r'^consumers/', include('consumers.urls')),
# url(r'^users/', include('users.urls')),
url(r'^users/', include('users.urls')),
# url(r'^api_calls/', include('api_calls.urls')),
# url(r'^api_config/', include('api_config.urls')),
]

View File

@ -15,15 +15,18 @@ class BaseFilter(object):
def apply(self, data):
filter_all = 'active_{}_all'.format(self.filter_type)
self.context[filter_all] = True
filter_active_value = 'active_{}'.format(self.filter_type)
self.context[filter_active_value] = 'All'
if not self.filter_type in self.request_get:
return data
filter_value = self.request_get[self.filter_type].lower()
if not filter_value or filter_value == 'all':
filter_value = self.request_get[self.filter_type]
if not filter_value or filter_value == 'All':
return data
self.context[filter_all] = False
self.context[filter_active_value] = filter_value
filter_active = 'active_{}_{}'.format(self.filter_type, filter_value)
self.context[filter_active] = True
return self._apply(data, filter_value)

View File

@ -10,7 +10,7 @@ body {
margin-bottom: 60px; /* footer height */
}
.footer {
footer {
position: absolute;
bottom: 0;
width: 100%;
@ -18,19 +18,20 @@ body {
height: 60px;
background-color: #333;
}
.footer p {
footer p {
margin-top: 25px;
font-size: 10px;
text-align: center;
color: #aaa;
}
.footer a:link, .footer a:visited {
footer a:link, .footer a:visited {
color: #ddd;
}
.footer a:hover, .footer a:focus {
footer a:hover, .footer a:focus {
color: #fff;
}
.navbar-brand img {
width: 20px;
height: 20px;
@ -49,10 +50,17 @@ body {
}
.navbar-default .navbar-nav > li > a:link, .navbar-default .navbar-nav > li > a:visited {
color: #000;
color: #fff;
}
.navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover {
color: #03749f;
color: #eee;
}
.navbar-default .navbar-toggle {
border-color: #fff;
}
.navbar-default .navbar-toggle .icon-bar {
background-color: #fff;
}
.btn-primary {

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="OBP API Manager" >
<meta name="author" content="TESOBE, Sebastian Henschel">
<title>{% block page_title %}OBP API Manager{% endblock page_title %}</title>
<title>{% block page_title %}API Manager{% endblock page_title %}</title>
<link rel="icon" type="image/png" href="{% static 'img/favicon.ico' %}" />
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'css/base.css' %}" rel="stylesheet">
@ -29,9 +29,9 @@
<ul class="nav navbar-nav">
{% url "consumers-index" as consumers_index_url %}
<li{% ifequal request.path consumers_index_url %} class="active" {% endifequal %}><a href="{{ consumers_index_url }}">Consumers</a></li>
{% comment %}
{% url "users-index" as users_index_url %}
<li{% ifequal request.path users_index_url %} class="active" {% endifequal %}><a href="{{ users_index_url }}">Users</a></li>
{% comment %}
{% url "api-calls-grouped" as api_calls_grouped_url %}
<li{% ifequal request.path api_calls_grouped_url %} class="active" {% endifequal %}><a href="{{ api_calls_grouped_url }}">API Calls Grouped</a></li>
{% url "api-calls-list" as api_calls_list_url %}
@ -66,7 +66,7 @@
{% block content %}{% endblock content %}
</div>
<footer class="footer">
<footer>
<div class="container">
<p class="text-muted">
<a title="API ROOT" href="{{ API_ROOT }}">API ROOT: {{ API_ROOT }}</a> |

View File

@ -33,6 +33,10 @@ def api_get(request, urlpath=''):
return api_call(request, 'GET', urlpath)
def api_delete(request, urlpath):
return api_call(request, 'DELETE', urlpath)
def api_post(request, urlpath, payload):
return api_call(request, 'POST', urlpath, payload)
@ -41,6 +45,7 @@ def api_put(request, urlpath, payload):
return api_call(request, 'PUT', urlpath, payload)
def api_call(request, method='GET', urlpath='', payload=None):
url = settings.OAUTH_API + settings.OAUTH_API_BASE_PATH + urlpath
api_log(logging.INFO, '{} {}'.format(method, url))
@ -63,6 +68,8 @@ def api_call(request, method='GET', urlpath='', payload=None):
msg = 'Ran into a {}: {}'.format(response.status_code, response.text)
api_log(logging.ERROR, msg)
data = response.text
elif response.status_code in [204]:
data = response.text
else:
data = response.json()
return data

View File

@ -1,21 +1,3 @@
.users #user-list {
#users #users-list {
margin-top: 20px;
}
.users #user-detail {
display: none;
}
.users #entitlement-template {
display: none;
}
.users .form-inline .form-group {
display: block;
}
.user-row.active {
background-color: #ddd;
}

View File

@ -1,40 +1,2 @@
$(document).ready(function($) {
$('#user-detail').hide();
$('.users .user-row').click(function() {
$('.users .user-row').removeClass('active');
$(this).addClass('active');
var user_id = $(this).data('user-id')
$.each(USERS, function (idx, user) {
if (user['id'] == user_id) {
$('#user-detail-id').val(user['id']);
$('#user-detail-name').html(user['name_']);
var isSuperAdmin = $('#user-detail-is-super-admin');
if (user['is_super_admin']) {
isSuperAdmin.html('Super Admin');
} else {
isSuperAdmin.html('');
}
$('#user-detail-email').html(user['email']);
$('#user-detail-last-login').html(user['last_login']);
$('#user-detail .entitlement').remove();
$.each(user['entitlements'], function(idx, entitlement) {
var role_name = entitlement['role_name'];
console.log('entitlement ' + entitlement);
var formgroup = $('#entitlement-template').clone();
formgroup.removeAttr('id');
formgroup.addClass('entitlement');
var checkbox = formgroup.find('input');
checkbox.prop('checked', true);
checkbox.attr('name', 'entitlement-' + role_name);
checkbox.attr('id', 'user-detail-entitlement-' + role_name);
var label = formgroup.find('label');
label.attr('for', 'user-detail-entitlement-' + role_name);
label.html(role_name);
formgroup.appendTo('#user-detail form');
});
$('#user-detail').show();
}
});
});
});

View File

@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% load static %}
{% block page_title %}{{ block.super }} / {{ user.username }}{% endblock page_title %}
{% block content %}
<div id="users-detail">
<h1>User {{ user.username }}</h1>
<div id="users-detail-user_id">
<strong>User ID</strong><br />
<span>{{ user.user_id }}</span>
</div>
<div id="users-detail-email">
<strong>Email</strong><br />
<span>{{ user.email }}</span>
</div>
{% if user.user_id %}
<div id="users-detail-entitlements">
<h2>Add Entitlement</h2>
<form class="form-inline" action="{% url 'users-add-entitlement' user.user_id %}" method="post">
{% csrf_token %}
<input type="hidden" name="user_email" value="{{ user.email }}" />
<div class="form-group">
<label for="users-detail-entitlement-role_name">Role name</label> <input type="text" class="form-control" name="role_name" id="users-detail-entitlements-role_name" aria-label="active" />
</div>
<div class="form-group">
<label for="users-detail-entitlement-bank_id">Bank Id</label> <input type="text" class="form-control" name="bank_id" id="users-detail-entitlement-bank_id" aria-label="active" placeholder="Empty if not bank-specific"/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-green">Add</a>
</div>
</form>
<h2>Entitlements</h2>
<table class="table table-striped">
<tbody>
{% for entitlement in user.entitlements %}
<tr>
<td>{{ entitlement.role_name }}</td>
<td>
{# SuperAdmin has no entitlement_id! #}
{% if entitlement.entitlement_id %}
<form action="{% url 'users-delete-entitlement' user.user_id entitlement.entitlement_id %}" method="post">
{% csrf_token %}
<input type="hidden" name="user_email" value="{{ user.email }}" />
<input type="hidden" name="role_name" value="{{ entitlement.role_name }}" />
<button type="submit" class="btn btn-primary btn-red">Delete</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endblock %}
{% block extrajs %}
{% comment %}
<script type="text/javascript" src="{% static 'users/js/users.js' %}"></script>
<script type="text/javascript">
</script>
{% endcomment %}
{% endblock extrajs %}
{% block extracss %}
<link href="{% static 'users/css/users.css' %}" rel="stylesheet">
{% endblock extracss %}

View File

@ -1,93 +1,62 @@
{% extends 'base.html' %}
{% load humanize static %}
{% load static %}
{% block page_title %}{{ block.super }} / Users{% endblock page_title %}
{% block content %}
<div class="users">
<div id="users">
<h1>Users</h1>
<div class="alert alert-warning">
The only live data here is the role names!
</div>
<div class="btn-group" role="group" id="filter-time" aria-label="filter-time">
<a href="?time=minute&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_minute %} active{% endif %}">Last Minute</a>
<a href="?time=hour&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_hour %} active{% endif %}">Last Hour</a>
<a href="?time=day&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_day %} active{% endif %}">Last Day</a>
<a href="?time=week&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_week %} active{% endif %}">Last Week</a>
<a href="?time=month&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_month %} active{% endif %}">Last Month</a>
<a href="?time=year&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_year %} active{% endif %}">Last Year</a>
<a href="?time=all&role_name={{ request.GET.role_name }}" class="btn btn-default{% if active_time_all %} active{% endif %}">All</a>
</div>
<div class="btn-group pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" id="filter-entitlement" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ active_role_name }} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="?role_name=all&time={{ request.GET.time }}">All</a></li>
<li><a href="">All</a></li>
<li role="separator" class="divider"></li>
{% for role_name in role_names %}
<li><a href="?role_name={{ role_name }}&time={{ request.GET.time }}">{{ role_name }}</a></li>
<li><a href="?role_name={{ role_name }}">{{ role_name }}</a></li>
{% endfor %}
</ul>
</div>
<h2>Statistics</h2>
<ul id="statistics">
<li>Total number of users: {{ statistics.users_num }}
</ul>
<table class="table" id="user-list">
<thead>
</thead>
<tbody>
{% for user in users %}
<tr class="user-row" data-user-id="{{ user.id }}">
<td>{{ user.name_ }}</td>
<td>{% if user.is_super_admin %}Super Admin{% endif %}</td>
<td>{{ user.email }}</td>
<td>{{ user.last_login|naturaltime }}</td>
<td>{{ user.whatever }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="user-detail">
<h2>Details</h2>
<form class="form-inline">
<input type="hidden" name="id" id="user-detail-id" value="" />
<table class="table">
<thead>
</thead>
<tbody>
<tr>
<td id="user-detail-name"></td>
<td id="user-detail-is-super-admin"></td>
<td id="user-detail-email"></td>
<td id="user-detail-last-login"></td>
</tr>
</tbody>
</table>
<div class="form-group" id="entitlement-template">
<input type="checkbox" class="form-control" name="" id="" aria-label="active" disabled="disabled" />
<label for="user-detail-entitlement" class="control-label"></label>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover" id="users-list">
<thead>
<th>#</th>
<th>User ID</th>
<th>Username</th>
<th>Email</th>
<th>Action</th>
</thead>
<tbody>
{% for user in users %}
<tr data-user-id="{{ user.user_id }}">
{% url 'users-detail' user.email as url_users_detail %}
<td>{{ forloop.counter }}</td>
<td><a href="{{ url_users_detail }}">{{ user.user_id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td><a href="{{ url_users_detail }}" class="btn btn-primary">View</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extrajs %}
{% comment %}
<script type="text/javascript" src="{% static 'users/js/users.js' %}"></script>
<script type="text/javascript">
USERS = {{ users_json|safe }};
</script>
{% endcomment %}
{% endblock extrajs %}

View File

@ -1,7 +1,18 @@
from django.conf.urls import url
from .views import IndexView
from .views import IndexView, DetailView, AddEntitlementView, DeleteEntitlementView
urlpatterns = [
url(r'^$', IndexView.as_view(), name='users-index'),
url(r'^$',
IndexView.as_view(),
name='users-index'),
url(r'^(?P<user_email>[\w\@\.\+-]+)$',
DetailView.as_view(),
name='users-detail'),
url(r'^(?P<user_id>[\w-]+)/entitlement/add$',
AddEntitlementView.as_view(),
name='users-add-entitlement'),
url(r'^(?P<user_id>[\w-]+)/entitlement/delete/(?P<entitlement_id>[\w-]+)$',
DeleteEntitlementView.as_view(),
name='users-delete-entitlement'),
]

View File

@ -7,88 +7,120 @@ from datetime import datetime
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import TemplateView, RedirectView, View
from base.filters import filter_time
from base.utils import json_serial, api_get
from base.filters import BaseFilter
from base.utils import api_get, api_post, api_delete
APIUSER = [
{
'id': 1,
'email': 'sebastian@tesobe.com',
'name_': 'Sebastian Henschel',
'userid_': 'zyzop',
#'userid_': '05c98d97-5d6d-46b5-a880-2da0a45019c8',
'last_login': '2016-09-07 12:34:47',
'whatever': 'dfasdfsd',
},
{
'id': 2,
'email': 'robert.x.0.gh@example.com',
'name_': 'Robert X.0.GH',
'userid_': 'ff3f94f9-bbd6-4cd9-ab78-3701e961eb58',
'last_login': '2016-08-10 16:34:47',
'whatever': 'ijkjkljljk',
},
]
class FilterRoleName(BaseFilter):
filter_type = 'role_name'
def _apply(self, data, filter_value):
filtered = [x for x in data if filter_value in [e['role_name'] for e in x['entitlements']['list']]]
return filtered
class IndexView(LoginRequiredMixin, TemplateView):
template_name = "users/index.html"
def filter_role_name(self, context, data):
context['active_role_name'] = 'All'
if not 'role_name' in self.request.GET:
return data
role_name = self.request.GET['role_name']
if not role_name or role_name.lower() == 'all':
return data
filtered = []
for user in data:
for entitlement in user['entitlements']:
if role_name == entitlement['role_name']:
filtered.append(user)
break
if filtered:
context['active_role_name'] = role_name
return filtered
def scrub(self, data):
for user in data:
user['last_login'] = datetime.strptime(user['last_login'], '%Y-%m-%d %H:%M:%S')
return data
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
filtered = deepcopy(APIUSER)
role_names = []
filtered = []
urlpath = '/users'
users = api_get(self.request, urlpath)
for user in filtered:
urlpath = '/users/{}/entitlements'.format(user['userid_'])
entitlements = api_get(self.request, urlpath)
if 'error' in entitlements:
messages.error(self.request, entitlements['error'])
break
else:
user['entitlements'] = []
for entitlement in entitlements['list']:
user['entitlements'].append(entitlement)
if entitlement['role_name'] == 'SuperAdmin':
user['is_super_admin'] = True
if not isinstance(users, dict):
messages.error(self.request, users)
elif 'error' in users:
messages.error(self.request, users['error'])
else:
for user in users['users']:
for entitlement in user['entitlements']['list']:
role_names.append(entitlement['role_name'])
role_names = list(set(role_names))
role_names.sort()
role_names = list(set(role_names))
role_names.sort()
filtered = FilterRoleName(context, self.request.GET)\
.apply(users['users'])
filtered = self.filter_role_name(context, filtered)
filtered = filter_time(self.request, context, filtered, 'last_login')
filtered = self.scrub(filtered)
context.update({
'role_names': role_names,
'statistics': {
'users_num': len(filtered),
},
'users': filtered,
'users_json': json.dumps(filtered, default=json_serial),
})
return context
class DetailView(LoginRequiredMixin, TemplateView):
template_name = 'users/detail.html'
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
# NOTE: assuming there is just one user with that email address
# The API actually needs a 'get user by id'
urlpath = '/users/{}'.format(kwargs['user_email'])
users = api_get(self.request, urlpath)
user = {}
if 'error' in users:
messages.error(self.request, users['error'])
elif len(users['users']) > 0:
user = users['users'][0]
urlpath = '/users/{}/entitlements'.format(user['user_id'])
entitlements = api_get(self.request, urlpath)
if 'error' in entitlements:
messages.error(self.request, entitlements['error'])
else:
user['entitlements'] = entitlements['list']
context.update({
'user': user,
})
return context
class AddEntitlementView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
urlpath = '/users/{}/entitlements'.format(kwargs['user_id'])
payload = {
'bank_id': request.POST['bank_id'],
'role_name': request.POST['role_name'],
}
entitlement = api_post(request, urlpath, payload=payload)
if not isinstance(entitlement, dict):
messages.error(request, entitlement)
elif 'error' in entitlement:
messages.error(request, entitlement['error'])
else:
msg = 'Entitlement with role {} has been added.'.format(
entitlement['role_name'])
messages.success(request, msg)
redirect_url = reverse('users-detail', kwargs={
'user_email': request.POST['user_email'],
})
return HttpResponseRedirect(redirect_url)
class DeleteEntitlementView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
urlpath = '/users/{}/entitlement/{}'.format(
kwargs['user_id'], kwargs['entitlement_id'])
result = api_delete(request, urlpath)
if 'error' in result:
messages.error(request, result['error'])
else:
msg = 'Entitlement with role {} has been deleted.'.format(
request.POST['role_name'])
messages.success(request, msg)
redirect_url = reverse('users-detail', kwargs={
'user_email': request.POST['user_email'],
})
return HttpResponseRedirect(redirect_url)