manager: implement role view

This commit is contained in:
Benjamin Dauvergne 2014-07-18 20:32:13 +02:00
parent c9716ef62b
commit dd1ffa8a04
19 changed files with 772 additions and 1 deletions

View File

@ -0,0 +1,18 @@
import sys
class AppSettings(object):
__PREFIX = 'A2_MANAGER_'
__DEFAULTS = {
'HOMEPAGE_URL': None,
'LOGOUT_URL': None,
}
def __getattr__(self, name):
from django.conf import settings
if name not in self.__DEFAULTS:
raise AttributeError
return getattr(settings, self.__PREFIX + name, self.__DEFAULTS[name])
app_settings = AppSettings()
app_settings.__name__ = __name__
sys.modules[__name__] = app_settings

View File

@ -0,0 +1,20 @@
from django_select2 import AutoSelect2Field, NO_ERR_RESP
from . import utils
class ChooseUserField(AutoSelect2Field):
def security_check(self, request, *args, **kwargs):
return True
def get_results(self, request, term, page, context):
return (NO_ERR_RESP, False, [(u.ref, u.name) for u in utils.search_user(term)])
def get_val_txt(self, value):
"""
The problem of issue #66 was here. I was not overriding this.
When using AutoSelect2MultipleField you should implement get_val_txt in this case.
I think that this is because there should be an unique correspondence between
the referenced value and the shown value
In this particular example, the referenced value and the shown value are the same
"""
return unicode(value)

View File

@ -0,0 +1,17 @@
from django.utils.translation import ugettext_lazy as _
from django import forms
from . import utils, fields
class RoleAddForm(forms.Form):
name = forms.CharField(
label=_('Role name'))
def save(self):
return utils.role_add(self.cleaned_data['name'])
class ChooseUserForm(forms.Form):
user = fields.ChooseUserField(label=_('user'))

View File

View File

@ -0,0 +1,77 @@
function displayPopup(event)
{
var $anchor = $(this);
var url = $anchor.attr('href');
var selector = $anchor.data('selector') || 'form';
function ajaxform_submit (data, status, xhr, form) {
if ('location' in data) {
var location = $.url(data.location);
var href = $.url(window.location.href);
if (location.attr('protocol') == href.attr('protocol') &&
location.attr('host') == href.attr('host') &&
location.attr('relative') == href.attr('relative')) {
var e = $.Event('popup-success');
$anchor.trigger(e);
if (! e.isDefaultPrevented()) {
window.location.reload(true);
}
}
// set anchor if it changed
window.location = data.location;
} else {
var html = data.content;
$(form).empty().append($(html).find(selector).children());
$(form).find('.buttons').hide();
}
}
$.ajax({
url: url,
success: function(html) {
var is_json = typeof html != 'string';
if (is_json) {
var html = html.content;
} else {
var html = html;
}
var form = $(html).find(selector);
var title = $(html).find('#appbar h2').text();
var dialog = $(form).dialog({modal: true, 'title': title, width: 'auto'});
var buttons = Array();
if (! form.prop('action')) {
form.prop('action', url);
}
$(dialog).find('.buttons').hide();
$(html).find('.buttons button, .buttons a').each(function(idx, elem) {
var button = Object();
button.text = $(elem).text();
if ($(elem).hasClass('cancel')) {
button.click = function() { dialog.dialog('destroy'); return false; };
} else {
button.click = function() { form.find('button').click(); return false; };
}
if ($(elem).hasClass('submit-button')) {
button.class = 'submit-button';
} else if ($(elem).hasClass('delete-button')) {
button.class = 'delete-button';
}
buttons.push(button);
});
buttons.reverse();
$(dialog).dialog('option', 'buttons', buttons);
if ($(dialog).find('input:visible').length) {
$(dialog).find('input:visible')[0].focus();
}
if (is_json && $.fn.url != undefined && $.fn.ajaxForm != undefined) {
$(form).ajaxForm({success: ajaxform_submit});
}
return false;
}
});
return false;
}
$(function() {
$('a[rel=popup]').click(displayPopup);
});

View File

@ -0,0 +1,9 @@
import django_tables2 as tables
from django.contrib.auth.models import User
class UserTable(tables.Table):
class Meta:
model = User
attrs = {'class': 'main', 'id': 'user-table'}
fields = ('username', 'email', 'first_name', 'last_name',
'is_active')

View File

@ -0,0 +1,185 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<title>Portail Admin</title>
<link rel="stylesheet" type="text/css" media="all" href="css/style.css"/>
<script src="js/jquery-1.7.2.min.js"></script>
<script src="js/jquery-ui-1.8.21.custom.min.js"></script>
<link rel="stylesheet" type="text/css" media="all" href="css/smoothness/jquery-ui-1.8.21.custom.css"/>
<link rel="stylesheet" type="text/css" media="all" href="js/select2/select2.css"/>
<script src="js/select2/select2.js"></script>
<script src="js/mockups.js"></script>
<script>
function unselect_all() {
$('.popover-container').css('display', 'none');
$('table tbody tr').removeClass('active');
$('table tbody tr input:checked').parents('tr').addClass('active');
return true;
}
$(function() {
$('select').select2();
$('#add-role-btn').click(function() {
unselect_all();
$('#role-add').dialog({title: 'Ajouter un rôle',
width: '400px',
modal: true,
buttons: [
{ text: "Annuler",
click: function() { $(this).dialog("close"); } },
{ text: "Ajouter",
click: function() { $(this).dialog("close"); } }]}
);
return false;
});
$('ul.roles a').click(function() {
unselect_all();
$('ul.roles a').removeClass('active');
$(this).addClass('active');
$('div.big-msg-info').hide();
$('div.role-info h3').text('Utilisateurs disposant du rôle : ' + $(this).text());
$('div.role-info').show();
return false;
});
$('html').click(unselect_all);
$('table td:first-child').click(function(ev) {ev.stopPropagation();});
$('table td:first-child input').change(function(ev) {
ev.stopPropagation();
$('.popover-container').css('display', 'none');
if ($(this).prop('checked')) {
$(this).prop('checked', true);
$(this).parents('tr').addClass('active');
} else {
$(this).prop('checked', false);
$(this).parents('tr').removeClass('active');
}
return true;
});
$('table tbody tr').click(function(ev) {
if ($(this).find('input:checked').length == 0) {
$('table tbody tr input:checked').parents('tr').removeClass('active');
$('table tbody tr input:checked').prop('checked', false);
if ($(this).hasClass('active')) {
$('.popover-container').css('display', 'none');
$(this).removeClass('active');
return false;
}
$('table tbody tr').removeClass('active');
}
$(this).addClass('active');
$('.popover-container').css('visibility', 'hidden').css('display', 'block');
$('.popover-container').css('left', ev.pageX - $('#content').offset().left - 20
).css('top', ev.pageY - $('#content').offset().top +
$('.popover').height() + 25
).css('visibility', 'visible');
return false;
});
});
</script>
</head>
<body>
<div id="wrap-large">
<div id="header">
<h1><a href="accueil.html">Portail Admin</a></h1>
</div>
<div id="splash" class="cmpp">
<div id="user-links">
<a href="index.html">Déconnexion</a>
</div>
</div>
<div id="more-user-links">
<a href="accueil.html" class="icon-home-space">Accueil</a>
</div>
<div id="content">
<div id="appbar">
<h2>Liste des rôles</h2>
</div>
<div id="sidebar">
<ul class="roles">
<li><a href="#">Administrateur</a></li>
<li><a href="#">Gestionnaire des annonces</a></li>
<li><a href="#">Gestionnaire des rôles</a></li>
<li><a href="#">Gestionnaire des sites communaux</a></li>
<li><a href="#">Gestionnaire des textes</a></li>
<li><a href="#">Gestionnaire des utilisateurs</a></li>
</ul>
</div>
<div class="content">
<div class="popover-container">
<ul class="popover">
<li><a>Retirer</a></li>
</ul>
</div>
<div class="big-msg-info">
Utilisez les filtres sur sur la gauche de l'écran pour afficher
les membres du rôle correspondant.
</div>
<div class="role-info" style="display: none;">
<h3></h3>
<table class="main">
<thead>
<tr>
<th></th>
<th>Nom d'utilisateur</th>
<th>Email</th>
<th>Prénom</th>
<th>Nom</th>
<th>Actif</th>
</tr>
</thead>
<tbody>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
<tr><td class="checkbox"><input type="checkbox"/></td><td>foobar</td><td>foobar@example.net</td><td>Foo</td><td>Bar</td><td>×</td></tr>
</tbody>
</table>
<form id="add-user-role">
Ajouter un utilisateur à ce rôle :
<select><option></option><option>foobar</option></select>
<button>Valider</button>
</form>
</div>
</div>
</div>
<div id="footer">
Logiciel Portail Admin - Copyright &copy; 2014 Entr'ouvert
</div>
</div>
<div id="role-edit" style="display: none;">
<form>
<label><span>Nom :</span> <input type="text" size="30" value="Foo"/></label></br>
</form>
</div>
<div id="role-add" style="display: none;">
<form>
<label><span>Nom du rôle :</span> <input type="text" size="30"/></label></br>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,83 @@
{% load i18n staticfiles %}<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<title>{% block title %}{% trans "Management" %}{% endblock %}</title>
<link rel="stylesheet" type="text/css" media="all" href="{% static "authentic2/manager/css/style.css" %}"/>
{% if debug %}
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<script src="{% static "jquery/js/jquery.form.js" %}"></script>
<script src="{% static "jquery-ui-contextmenu-1.5.0/jquery.ui-contextmenu.js" %}"></script>
{% else %}
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
<script src="{% static "jquery/js/jquery.form.min.js" %}"></script>
<script src="{% static "jquery-ui-contextmenu-1.5.0/jquery.ui-contextmenu.min.js" %}"></script>
{% endif %}
<script src="{% static "authentic2/js/purl.js" %}"></script>
<script src="{% static "authentic2/manager/js/manager.js" %}"></script>
<link rel="stylesheet" type="text/css" media="all" href="http://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"/>
{% block extra_scripts %}
{% endblock %}
</head>
<body>
<div id="wrap-large">
<div id="header">
{% block header %}<h1><a href="{{ management_homepage_url }}">{% trans "Management" %}</a></h1>{% endblock %}
</div>
<div id="splash" class="cmpp">
{% block splash %}
<div id="user-links">
{% block user-links %}
<a href="{{ management_logout_url }}">{% trans "Logout" %}</a>
{% endblock %}
</div>
{% endblock %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div id="more-user-links">
{% block more-user-links %}
<a href="{{ management_homepage_url }}" class="icon-home-space">{% trans "Homepage" %}</a>
{% endblock %}
</div>
<div id="content">
<div id="appbar">
{% block appbar %}
<h2>{% block page_title %}{% endblock %}</h2>
{% endblock %}
</div>
<div id="sidebar">
{% block sidebar %}
{% endblock %}
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
<div id="footer">
Authentic2 Manager - Copyright &copy; 2014 Entr'ouvert
</div>
</div>
{% block end_content%}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,18 @@
{% extends "authentic2/manager/base.html" %}
{% load i18n %}
{% block content %}
<div id="content">
{% if title %}
<div id="appbar"><h2>{{ title }}</h2></div>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button>{% trans "Create" %}</button>
<a class="cancel" href="..">Annuler</a>
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,85 @@
{% extends "authentic2/manager/roles.html" %}
{% load i18n staticfiles django_tables2 %}
{% block extra_scripts %}
{{ block.super }}
<script>
$(function () {
$('#search-input').change(function () {
var params = $.url().param();
if ($(this).val()) {
params.search = $(this).val();
} else {
if ('search' in params) {
delete params.search;
}
}
var href = $.url().attr('path')
if ($.param(params)) {
href += '?' + $.param(params);
}
window.location.href = href;
});
$('#search-input-clear-btn').click(function () {
$('#search-input').val('').trigger('change');
});
$('.content').on('click', '.paginator a', function () {
var href = $(this).attr('href');
$.get(href, '', function (response_text) {
var content = $(response_text).find('.table-container');
$('.table-container').replaceWith(content);
history.pushState(null, href, href);
setup_menu();
});
return false;
});
function remove_user(event, ui) {
var user = $(ui.target).parent('tr').data('ref');
if (! user) {
return;
}
$.post('', {
csrfmiddlewaretoken: '{{ csrf_token }}',
action: 'remove',
'user': user
},
function () {
$.get('', function (html) {
$('#user-table').replaceWith($(html).find('#user-table'));
setup_menu();
});
}
)
}
function setup_menu() {
$('#user-table').contextmenu({
delegate: 'tbody tr',
menu: [
{title: '{% trans "Remove" %}', action: remove_user}
]
});
}
setup_menu();
})
</script>
{{ choose_user_form.media }}
{% endblock %}
{% block content %}
<div class="role-info">
<h3>{% trans "Users with role" %}: {{ active_role.name }}</h3>
<div id="search-form">
{% trans "Search" %}: <input id="search-input" type="text" value="{{ request.GET.search }}"><button id="search-input-clear-btn">Effacer</button>
</div>
{% render_table users "authentic2/manager/table.html" %}
<form method="post" id="add-user-role">
Ajouter un utilisateur à ce rôle :
{% csrf_token %}
{{ choose_user_form }}
<button>Valider</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,55 @@
{% extends "authentic2/manager/base.html" %}
{% load i18n staticfiles %}
{% block title %}{{ block.super }} - {% trans "Role management" %}{% endblock %}
{% block page_title %}{% trans "Role management" %}{% endblock %}
{% block extra_scripts %}
<script>
$(function () {
function refresh_page() {
$.get('', '', function (response_text) {
$('#sidebar').replaceWith($(response_text).find('#sidebar'));
});
};
});
</script>
{% endblock %}
{% block sidebar %}
<ul class="roles">
{% for role in roles %}
<li>
<a href="{% url "a2-manager-role" role_ref=role.ref %}"
{% if role.ref == active_role.ref %}class="active"{% endif %}>
{{ role.name }}
</a>
</li>
{% endfor %}
</ul>
<a href="{% url "a2-manager-role-add" %}" rel="popup">{% trans "Add role" %}</a>
{% endblock %}
{% block content %}
<div class="big-msg-info">
Utilisez les filtres sur sur la gauche de l'écran pour afficher
les membres du rôle correspondant.
</div>
{% endblock %}
{% block end_content %}
<div id="role-edit" style="display: none;">
<form>
<label><span>Nom :</span> <input type="text" size="30" value="Foo"/></label></br>
</form>
</div>
<div id="role-add" style="display: none;">
<form id="role-add-form" method="post" action="{% url "a2-manager-role-add" %}">
{% csrf_token %}
{{ role_add_form }}
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% extends "django_tables2/table.html" %}
{% load django_tables2 %}
{% block table.tbody.row %}
<tr data-ref="{{ row.record.id }}" class="{{ forloop.counter|divisibleby:2|yesno:"even,odd" }}"> {# avoid cycle for Django 1.2-1.6 compatibility #}
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% block pagination %}
<p class="paginator">
{% if table.page.number > 1 %}
{% if table.page.previous_page_number != 1 %}
<a href="{% querystring table.prefixed_page_field=1 %}">1</a>
...
{% endif %}
{% endif %}
{% if table.page.has_previous %}
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">{{ table.page.previous_page_number }}</a>
{% endif %}
<span class="this-page">{{ table.page.number }}</span>
{% if table.page.has_next %}
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">{{ table.page.next_page_number }}</a>
{% endif %}
{% if table.page.number != table.page.paginator.num_pages %}
{% if table.page.paginator.num_pages > 1 %}
{% if table.page.next_page_number != table.page.paginator.num_pages %}
...
<a href="{% querystring table.prefixed_page_field=table.page.paginator.num_pages %}">{{ table.page.paginator.num_pages }}</a>
{% endif %}
{% endif %}
{% endif %}
</p>
{% endblock %}

View File

@ -0,0 +1,10 @@
from django.conf.urls import patterns, url, include
from . import views
urlpatterns = patterns('authentic2.views',
url(r'^roles/$', views.roles, name='a2-manager-roles'),
url(r'^roles/add/$', views.role_add, name='a2-manager-role-add'),
url(r'^roles/(?P<role_ref>[^/]*)/$', views.role, name='a2-manager-role'),
url(r'^', include('django_select2.urls')),
)

View File

@ -0,0 +1,44 @@
from django.contrib.auth.models import Group, User
from django.db.models.query import Q
class Role(object):
def __init__(self, name, ref):
self.name = name
self.ref = ref
class RoleUser(Role):
pass
def get_roles():
return [Role(g.name, g.id) for g in Group.objects.order_by('name')]
def get_role(ref):
g = Group.objects.get(id=ref)
return Role(g.name, g.id)
def filter_user(qs, search):
return qs.filter(Q(username__contains=search)
| Q(first_name__contains=search)
| Q(last_name__contains=search)
| Q(email__contains=search))
def get_role_users(role, search=None):
qs = User.objects.filter(groups__id=role.ref)
if search:
qs = filter_user(qs, search)
return qs
def role_add(name):
g, created = Group.objects.get_or_create(name=name)
return g.id
def search_user(term):
return [RoleUser(u.get_full_name(), u.id) for u in filter_user(User.objects.all(), term)[:10]]
def add_user_to_role(role, user):
User.objects.get(id=user).groups.add(Group.objects.get(id=role.ref))
def remove_user_from_role(role, user):
User.objects.get(id=user).groups.remove(Group.objects.get(id=role.ref))

101
authentic2/manager/views.py Normal file
View File

@ -0,0 +1,101 @@
import json
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from django_tables2 import RequestConfig
from . import app_settings, utils, tables, forms
class ManagerMixin(object):
def get_context_data(self, **kwargs):
ctx = super(ManagerMixin, self).get_context_data(**kwargs)
ctx['management_homepage_url'] = app_settings.HOMEPAGE_URL or '/'
ctx['management_logout_url'] = app_settings.LOGOUT_URL or '/accounts/logout'
return ctx
class RolesMixin(ManagerMixin):
def get_context_data(self, **kwargs):
ctx = super(ManagerMixin, self).get_context_data(**kwargs)
ctx['roles'] = utils.get_roles()
ctx['role_add_form'] = forms.RoleAddForm()
return ctx
class AjaxFormViewMixin(object):
template_name = 'authentic2/manager/form.html'
success_url = '.'
def form_valid(self, form):
if hasattr(form, 'save'):
self.form_result = form.save()
return super(AjaxFormViewMixin, self).form_valid(form)
def dispatch(self, request, *args, **kwargs):
response = super(AjaxFormViewMixin, self).dispatch(request, *args, **kwargs)
if not request.is_ajax():
return response
data = {}
if 'Location' in response:
data['location'] = response['Location']
if hasattr(response, 'render'):
response.render()
data['content'] = response.content
return HttpResponse(json.dumps(data), content_type='application/json')
class RolesView(RolesMixin, TemplateView):
template_name = 'authentic2/manager/roles.html'
class TitleMixin(object):
title = None
def get_context_data(self, **kwargs):
ctx = super(TitleMixin, self).get_context_data(**kwargs)
if self.title:
ctx['title'] = self.title
return ctx
class RoleAddView(TitleMixin, AjaxFormViewMixin, FormView):
form_class = forms.RoleAddForm
title = _('Add new role')
def form_valid(self, form):
super(RoleAddView, self).form_valid(form)
return redirect('a2-manager-role', role_ref=self.form_result)
class RoleView(RolesMixin, TemplateView):
template_name = 'authentic2/manager/role.html'
def get_role(self):
return utils.get_role(self.kwargs['role_ref'])
def get_context_data(self, **kwargs):
ctx = super(RoleView, self).get_context_data(**kwargs)
ctx['active_role'] = self.get_role()
kwargs = {}
if 'search' in self.request.GET:
kwargs = {'search': self.request.GET['search']}
users = utils.get_role_users(ctx['active_role'], **kwargs)
table = tables.UserTable(users)
RequestConfig(self.request).configure(table)
ctx['users'] = table
ctx['choose_user_form'] = forms.ChooseUserForm()
return ctx
def post(self, request, *args, **kwargs):
role = self.get_role()
ref = request.POST.get('user')
if ref:
action = request.POST.get('action', 'add')
if action == 'add':
utils.add_user_to_role(role, ref)
elif action == 'remove':
utils.remove_user_from_role(role, ref)
return HttpResponseRedirect('')
roles = RolesView.as_view()
role_add = RoleAddView.as_view()
role = RoleView.as_view()

View File

@ -173,6 +173,8 @@ INSTALLED_APPS = (
'admin_tools.dashboard',
'django.contrib.admin',
'registration',
'django_select2',
'django_tables2',
'authentic2.nonce',
'authentic2.saml',
'authentic2.idp',
@ -180,6 +182,7 @@ INSTALLED_APPS = (
'authentic2.auth2_auth',
'authentic2.attribute_aggregator',
'authentic2.disco_service',
'authentic2.manager',
'authentic2',
)

View File

@ -23,6 +23,7 @@ not_homepage_patterns += patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^admin_tools/', include('admin_tools.urls')),
url(r'^idp/', include('authentic2.idp.urls')),
url(r'^manager/', include('authentic2.manager.urls')),
)
if getattr(settings, 'AUTH_OPENID', False):

View File

@ -8,3 +8,5 @@ django-debug-toolbar<1.0.0
--allow-unverified django-admin-tools
django-admin-tools>=0.5.1
dnspython
django-select2
django-tables2

View File

@ -120,7 +120,10 @@ setup(name="authentic2",
'django-registration>=1',
'django-admin-tools>=0.5.1',
'django-debug-toolbar<1.0.0',
'dnspython',],
'dnspython',
'django-select2',
'django-tables2',
],
zip_safe=False,
classifiers=[
"Development Status :: 5 - Production/Stable",