0004-manager-implement-all-views.patch
authentic2/manager/app_settings.py | ||
---|---|---|
1 |
import sys |
|
2 | ||
3 |
class AppSettings(object): |
|
4 |
__PREFIX = 'A2_MANAGER_' |
|
5 |
__DEFAULTS = { |
|
6 |
'HOMEPAGE_URL': None, |
|
7 |
'LOGOUT_URL': None, |
|
8 |
} |
|
9 | ||
10 |
def __getattr__(self, name): |
|
11 |
from django.conf import settings |
|
12 |
if name not in self.__DEFAULTS: |
|
13 |
raise AttributeError |
|
14 |
return getattr(settings, self.__PREFIX + name, self.__DEFAULTS[name]) |
|
15 | ||
16 |
app_settings = AppSettings() |
|
17 |
app_settings.__name__ = __name__ |
|
18 |
sys.modules[__name__] = app_settings |
authentic2/manager/fields.py | ||
---|---|---|
1 |
from django_select2 import AutoSelect2Field, AutoModelSelect2MultipleField, NO_ERR_RESP |
|
2 | ||
3 |
from django.contrib.auth.models import Group |
|
4 | ||
5 |
from . import utils |
|
6 | ||
7 |
class ChooseUserField(AutoSelect2Field): |
|
8 |
def security_check(self, request, *args, **kwargs): |
|
9 |
return True |
|
10 | ||
11 |
def get_results(self, request, term, page, context): |
|
12 |
return (NO_ERR_RESP, False, [(u.ref, u.name) for u in utils.search_user(term)]) |
|
13 | ||
14 |
def get_val_txt(self, value): |
|
15 |
""" |
|
16 |
The problem of issue #66 was here. I was not overriding this. |
|
17 |
When using AutoSelect2MultipleField you should implement get_val_txt in this case. |
|
18 |
I think that this is because there should be an unique correspondence between |
|
19 |
the referenced value and the shown value |
|
20 |
In this particular example, the referenced value and the shown value are the same |
|
21 |
""" |
|
22 |
return unicode(value) |
|
23 | ||
24 |
class GroupsField(AutoModelSelect2MultipleField): |
|
25 |
queryset = Group.objects |
|
26 |
search_fields = [ |
|
27 |
'name__icontains', |
|
28 |
] |
authentic2/manager/forms.py | ||
---|---|---|
1 |
from django.utils.translation import ugettext_lazy as _ |
|
2 |
from django import forms |
|
3 | ||
4 |
from authentic2.compat import get_user_model |
|
5 | ||
6 |
from . import utils, fields |
|
7 | ||
8 |
class RoleAddForm(forms.Form): |
|
9 |
name = forms.CharField( |
|
10 |
label=_('Role name')) |
|
11 | ||
12 |
def save(self): |
|
13 |
return utils.role_add(self.cleaned_data['name']) |
|
14 | ||
15 | ||
16 |
class ChooseUserForm(forms.Form): |
|
17 |
ref = fields.ChooseUserField(label=_('user')) |
|
18 | ||
19 | ||
20 |
class UserEditForm(forms.ModelForm): |
|
21 |
groups = fields.GroupsField(required=False) |
|
22 | ||
23 |
class Meta: |
|
24 |
model = get_user_model() |
|
25 |
fields = [ 'username', 'first_name', 'last_name', 'email', 'groups'] |
|
26 |
authentic2/manager/static/authentic2/manager/css/style.css | ||
---|---|---|
1 | ||
2 |
div#sidebar { |
|
3 |
width: 15% |
|
4 |
float: left; |
|
5 |
text-align: justify; |
|
6 |
padding-right: 10px; |
|
7 |
} |
|
8 | ||
9 |
div#sidebar { |
|
10 |
text-align: left; |
|
11 |
} |
|
12 | ||
13 |
div#sidebar input[type=text] { |
|
14 |
width: 95%; |
|
15 |
} |
|
16 | ||
17 |
div#sidebar button { |
|
18 |
width: 100%; |
|
19 |
} |
|
20 | ||
21 | ||
22 |
#sidebar ul.roles { |
|
23 |
padding: 0; |
|
24 |
text-align: left; |
|
25 |
list-style: none; |
|
26 |
} |
|
27 | ||
28 |
#sidebar ul.roles li { |
|
29 |
margin-bottom: 1ex; |
|
30 |
} |
|
31 | ||
32 |
#sidebar ul.roles li a { |
|
33 |
text-decoration: none; |
|
34 |
} |
|
35 | ||
36 |
#sidebar ul.roles a.active { |
|
37 |
font-weight: bold; |
|
38 |
} |
|
39 | ||
40 |
div.role-info h3 { |
|
41 |
} |
|
42 | ||
43 |
form#add-user-role { |
|
44 |
margin-top: 2em; |
|
45 |
margin-bottom: 2em; |
|
46 |
} |
|
47 | ||
48 |
form#add-user-role div.select2-container { |
|
49 |
width: 30%; |
|
50 |
display: inline-block; |
|
51 |
position: relative; |
|
52 |
top: 2px; |
|
53 |
margin-left: 1ex; |
|
54 |
margin-right: 1ex; |
|
55 |
} |
|
56 | ||
57 |
table.feeds td.url { |
|
58 |
text-align: left; |
|
59 |
padding-left: 1em; |
|
60 |
} |
|
61 | ||
62 |
form#errors label span, |
|
63 |
form#smtp label span { |
|
64 |
width: 16em; |
|
65 |
display: inline-block; |
|
66 |
} |
|
67 | ||
68 |
form#errors button, form#smtp button { |
|
69 |
margin-left: 15.4em; |
|
70 |
} |
|
71 | ||
72 |
div#user-edit div.left { |
|
73 |
float: left; |
|
74 |
width: 400px; |
|
75 |
} |
|
76 | ||
77 |
div#user-edit div.left button { |
|
78 |
margin-top: 2em; |
|
79 |
} |
|
80 | ||
81 |
div#user-edit div.right { |
|
82 |
float: right; |
|
83 |
width: 150px; |
|
84 |
} |
|
85 | ||
86 |
div#user-edit div.right strong { |
|
87 |
display: block; |
|
88 |
margin-bottom: 1ex; |
|
89 |
} |
|
90 | ||
91 |
div#user-edit div.right button { |
|
92 |
width: 100%; |
|
93 |
text-align: left; |
|
94 |
padding: 0.5ex 1ex; |
|
95 |
} |
|
96 | ||
97 |
table.users tbody tr td:nth-child(2) { |
|
98 |
color: #FF7800; |
|
99 |
} |
|
100 | ||
101 |
table.users tbody tr td:nth-child(2):hover { |
|
102 |
text-decoration: underline; |
|
103 |
border-bottom: 1px solid transparent; |
|
104 |
} |
|
105 | ||
106 |
/* bdauvergne additions */ |
|
107 | ||
108 |
#delete-form { |
|
109 |
float: right; |
|
110 |
} |
|
111 | ||
112 |
.ui-dialog-content .helptext { |
|
113 |
display: none; |
|
114 |
} |
|
115 | ||
116 |
.form-inner-container { |
|
117 |
width: 500px; |
|
118 |
} |
|
119 | ||
120 |
.other_actions { |
|
121 |
float: right; |
|
122 |
position: relative; |
|
123 |
max-width: 35%; |
|
124 |
} |
|
125 | ||
126 |
.other_actions input[type=submit] { |
|
127 |
display: block; |
|
128 |
margin: 5px; |
|
129 |
width: 100%; |
|
130 |
} |
|
131 | ||
132 |
ul.messages { |
|
133 |
position: fixed; |
|
134 |
width: 30em; |
|
135 |
top: 10px; |
|
136 |
right: 10px; |
|
137 |
padding: 0; |
|
138 |
z-index: 2000; |
|
139 |
margin-top: 1em; |
|
140 |
list-style-type: none; |
|
141 |
display: table; |
|
142 |
margin: auto; |
|
143 |
background: rgba(40, 40, 40, 0.7); |
|
144 |
color: white; |
|
145 |
text-shadow: black 1px 1px 1px; |
|
146 |
border-radius: 10px; |
|
147 |
} |
|
148 | ||
149 |
ul.messages li { |
|
150 |
padding: 1ex; |
|
151 |
margin: 1ex; |
|
152 |
border-size: 0px; |
|
153 |
-moz-border-radius:7px; |
|
154 |
-webkit-border-radius:7px; |
|
155 |
border-radius:7px; |
|
156 |
border: none; |
|
157 |
} |
|
158 | ||
159 |
input[type=submit][name=password_reset] { |
|
160 |
white-space: normal; |
|
161 |
} |
|
162 | ||
163 |
li#roles a { background-image: url(icon-personnes.png); } |
|
164 |
li#roles a:hover { background-image: url(icon-personnes-hover.png); } |
|
165 | ||
166 |
li#users a { background-image: url(icon-user.png); } |
|
167 |
li#users a:hover { background-image: url(icon-user-hover.png); } |
authentic2/manager/static/authentic2/manager/js/manager.js | ||
---|---|---|
1 |
$(function() { |
|
2 |
/* search inputs behaviours */ |
|
3 |
$('#search-input').change(function () { |
|
4 |
var params = $.url().param(); |
|
5 |
if ($(this).val()) { |
|
6 |
params.search = $(this).val(); |
|
7 |
} else { |
|
8 |
if ('search' in params) { |
|
9 |
delete params.search; |
|
10 |
} |
|
11 |
} |
|
12 |
var href = $.url().attr('path') |
|
13 |
if ($.param(params)) { |
|
14 |
href += '?' + $.param(params); |
|
15 |
} |
|
16 |
window.location = href; |
|
17 |
}); |
|
18 | ||
19 |
/* role/user table refresh */ |
|
20 |
function update_table(href, cb) { |
|
21 |
$.get(href, function (response_text) { |
|
22 |
var $response = $(response_text); |
|
23 |
var $content = $response.find('.table-container'); |
|
24 |
if (! $content.length) { |
|
25 |
$content = $response.find('table'); |
|
26 |
} |
|
27 |
var $container = $('.table-container'); |
|
28 |
if (! $container.length) { |
|
29 |
$container = $('table'); |
|
30 |
} |
|
31 |
$container.replaceWith($content); |
|
32 |
if (cb != undefined) { |
|
33 |
cb(); |
|
34 |
} |
|
35 |
}); |
|
36 |
} |
|
37 |
/* paginator ajax loading */ |
|
38 |
$('.content').on('click', '.paginator a', function () { |
|
39 |
var href = $(this).attr('href'); |
|
40 |
var title = $(this).text(); |
|
41 |
update_table(href, function () { |
|
42 |
history.pushState(null, 'page ' + title, href); |
|
43 |
}); |
|
44 |
return false; |
|
45 |
}); |
|
46 |
/* dialog load handler */ |
|
47 |
$(document).on('gadjo:dialog-loaded', function (e, form) { |
|
48 |
$('.messages', form).delay(3000*(1+$('.messages li', form).length)).fadeOut('slow'); |
|
49 |
if ($('.table-container').length) { |
|
50 |
update_table(location.href); |
|
51 |
} |
|
52 |
}); |
|
53 |
/* user deletion */ |
|
54 |
$(document).on('click', '.js-remove-user', function (e) { |
|
55 |
var $anchor = $(e.target); |
|
56 |
if ($(e.target).data('confirm')) { |
|
57 |
if (! confirm($(e.target).data('confirm'))) { |
|
58 |
return false; |
|
59 |
} |
|
60 |
} |
|
61 |
var $tr = $anchor.parents('tr'); |
|
62 |
var ref = $tr.data('ref'); |
|
63 |
$.post('', {'csrfmiddlewaretoken': window.csrf_token, 'action': 'remove', 'ref': ref}, function () { |
|
64 |
update_table(window.location.href); |
|
65 |
}); |
|
66 |
return false; |
|
67 |
}); |
|
68 |
/* confirmation on submit buttons */ |
|
69 |
$(document).on('click', 'input[type=submit]', function (e) { |
|
70 |
if ($(e.target).data('confirm')) { |
|
71 |
if (! confirm($(e.target).data('confirm'))) { |
|
72 |
return false; |
|
73 |
} |
|
74 |
} |
|
75 |
}) |
|
76 |
}); |
authentic2/manager/tables.py | ||
---|---|---|
1 |
from django.utils.translation import ugettext_lazy as _ |
|
2 |
from django.utils.safestring import mark_safe |
|
3 | ||
4 |
import django_tables2 as tables |
|
5 | ||
6 |
from authentic2.compat import get_user_model |
|
7 | ||
8 |
class UserTable(tables.Table): |
|
9 |
username = tables.TemplateColumn( |
|
10 |
'<a rel="popup" href="{% url "a2-manager-user-edit" pk=record.pk %}">{{ record.username }}</a>', |
|
11 |
verbose_name=_('username')) |
|
12 |
email = tables.Column(verbose_name=mark_safe(_('Email'))) |
|
13 | ||
14 |
class Meta: |
|
15 |
model = get_user_model() |
|
16 |
attrs = {'class': 'main', 'id': 'user-table'} |
|
17 |
fields = ('username', 'email', 'first_name', 'last_name', |
|
18 |
'is_active') |
|
19 |
empty_text = _('None') |
authentic2/manager/templates/authentic2/manager/base.html | ||
---|---|---|
1 |
{% extends "gadjo/base.html" %} |
|
2 |
{% load i18n staticfiles %} |
|
3 | ||
4 |
{% block page-title %}{% trans "Management" %}{% endblock %} |
|
5 | ||
6 |
{% block css %} |
|
7 |
<link rel="stylesheet" type="text/css" media="all" href="{% static "authentic2/manager/css/style.css" %}"/> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block extrascripts %} |
|
11 |
{% if debug %} |
|
12 |
<script src="{% static "jquery/js/jquery.form.js" %}"></script> |
|
13 |
{% else %} |
|
14 |
<script src="{% static "jquery/js/jquery.form.min.js" %}"></script> |
|
15 |
{% endif %} |
|
16 |
<script type="text/javascript" src="{% static "authentic2/js/purl.js" %}"></script> |
|
17 |
<script type="text/javascript" src="{% static "authentic2/manager/js/manager.js" %}"></script> |
|
18 |
<script type="text/javascript" src="/static/js/select2.js"></script> |
|
19 |
<script type="text/javascript" src="/static/js/heavy_data.js"></script> |
|
20 |
<script> |
|
21 |
window.csrf_token = '{{ csrf_token }}'; |
|
22 |
</script> |
|
23 |
{% endblock %} |
|
24 | ||
25 |
{% block site-url %}{{ management_homepage_url }}{% endblock %} |
|
26 |
{% block site-title %}{% trans "Management" %}{% endblock %} |
|
27 | ||
28 |
{% block logout-url %}{{ management_logout_url }}{% endblock %} |
|
29 | ||
30 |
{% block homepage-url %}{% url "a2-manager-homepage" %}{% endblock %} |
|
31 | ||
32 |
{% block appbar %} |
|
33 |
<h2>{% block page_title %}{% endblock %}</h2> |
|
34 |
{% endblock %} |
|
35 |
authentic2/manager/templates/authentic2/manager/delete.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/sidebar.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block messages %} |
|
5 |
{% endblock %} |
|
6 | ||
7 |
{% block main %} |
|
8 |
{% if title %} |
|
9 |
<div id="appbar"><h2>{{ title }}</h2></div> |
|
10 |
{% endif %} |
|
11 |
<form method="post"> |
|
12 |
{% csrf_token %} |
|
13 |
<div class="form-inner-container"> |
|
14 |
{% block caption %} |
|
15 |
<p>{% blocktrans %}Do you want to delete role {{ object }} ?{% endblocktrans %}</p> |
|
16 |
{% endblock %} |
|
17 |
<div class="buttons"> |
|
18 |
<button>{% trans "Delete" %}</button> |
|
19 |
<a class="cancel" href="..">{% trans "Cancel" %}</a> |
|
20 |
</div> |
|
21 |
</div> |
|
22 |
</form> |
|
23 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/form.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/sidebar.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block messages %} |
|
5 |
{% endblock %} |
|
6 | ||
7 |
{% block main %} |
|
8 |
<div class="content"> |
|
9 |
{% if title %} |
|
10 |
<div id="appbar"><h2>{{ title }}</h2></div> |
|
11 |
{% endif %} |
|
12 |
<form method="post"> |
|
13 |
<div class="form-inner-container"> |
|
14 |
{% if messages %} |
|
15 |
<ul class="messages"> |
|
16 |
{% for message in messages %} |
|
17 |
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}> |
|
18 |
{{ message }} |
|
19 |
</li> |
|
20 |
{% endfor %} |
|
21 |
</ul> |
|
22 |
{% endif %} |
|
23 |
{% if other_actions %} |
|
24 |
<div class="other_actions"> |
|
25 |
<strong>{% trans "Actions" %}</strong> |
|
26 |
{% for action in other_actions %} |
|
27 |
<input type="submit" name="{{ action.name }}" value="{{ action.title }}" |
|
28 |
{% if action.confirm %}data-confirm="{{ action.confirm }}"{% endif %} |
|
29 |
/> |
|
30 |
{% endfor %} |
|
31 |
</div> |
|
32 |
{% endif %} |
|
33 |
{% csrf_token %} |
|
34 |
{{ form.as_p }} |
|
35 |
<div class="buttons"> |
|
36 |
<button>{% if action %}{{ action }}{% else %}{% trans "Create" %}{% endif %}</button> |
|
37 |
<a class="cancel" href="..">{% trans "Cancel" %}</a> |
|
38 |
</div> |
|
39 |
</div> |
|
40 |
</form> |
|
41 |
</div> |
|
42 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/homepage.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block beforecontent %} |
|
5 |
{% endblock %} |
|
6 | ||
7 |
{% block appbar %} |
|
8 |
<h2>{% trans "Welcome" %}</h2> |
|
9 |
{% endblock %} |
|
10 | ||
11 |
{% block content %} |
|
12 |
<div id="content"> |
|
13 |
<div id="user-info"> |
|
14 |
{{ user.get_full_name }} |
|
15 |
(<a href="{% url "auth_password_change" %}">{% trans "Password change" %}</a>) |
|
16 |
</div> |
|
17 | ||
18 |
<ul class="apps"> |
|
19 |
<li id="users"><a href="{% url "a2-manager-users" %}">{% trans "User management" %}</a></li> |
|
20 |
<li id="roles"><a href="{% url "a2-manager-roles" %}">{% trans "Role management" %}</a></li> |
|
21 |
</ul> |
|
22 |
<br style="clear: both;"/> |
|
23 |
</div> |
|
24 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/role.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/roles.html" %} |
|
2 | ||
3 |
{% load i18n staticfiles django_tables2 %} |
|
4 | ||
5 |
{% block extra_scripts %} |
|
6 |
{{ block.super }} |
|
7 |
{{ choose_user_form.media }} |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block main %} |
|
11 |
<div class="role-info"> |
|
12 |
<h3 style="margin-top: 0;">{% trans "Users with role" %}: {{ active_role.name }}</h3> |
|
13 | ||
14 |
{% render_table users "authentic2/manager/role_users_table.html" %} |
|
15 | ||
16 |
<form method="post" id="add-user-role"> |
|
17 |
{% trans "Add an user to this role:" %} |
|
18 |
{% csrf_token %} |
|
19 |
{{ choose_user_form.ref }} |
|
20 |
<button>{% trans "Add" %}</button> |
|
21 |
</form> |
|
22 |
</div> |
|
23 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/role_users_table.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/table.html" %} |
|
2 | ||
3 |
{% load i18n %} |
|
4 | ||
5 |
{% block table.head.last.column %} |
|
6 |
<th></th> |
|
7 |
{% endblock %} |
|
8 |
{% block table.tbody.last.column %} |
|
9 |
<td><a class="icon-remove-sign js-remove-user" data-confirm="{% blocktrans with username=row.record.username %}Do you really want to delete user "{{ username }}" ?{% endblocktrans %}" href="#"></a></td> |
|
10 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/roles.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/sidebar.html" %} |
|
2 |
{% load i18n staticfiles %} |
|
3 | ||
4 |
{% block title %}{{ block.super }} - {% trans "Roles management" %}{% endblock %} |
|
5 | ||
6 |
{% block page_title %}{% trans "Roles management" %} |
|
7 |
{% if active_role %} — {{ active_role.name }}{% endif %} |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block appbar %} |
|
11 |
{{ block.super }} |
|
12 |
{% if active_role %} |
|
13 |
<a rel="popup" href="{% url "a2-manager-role-delete" role_ref=active_role.ref %}" id="add-user-btn">{% trans "Delete" %}</a> |
|
14 |
<a rel="popup" href="{% url "a2-manager-role-edit" role_ref=active_role.ref %}" id="add-user-btn">{% trans "Edit" %}</a> |
|
15 |
{% else %} |
|
16 |
<p><a href="{% url "a2-manager-role-add" %}" rel="popup">{% trans "Add role" %}</a></p> |
|
17 |
{% endif %} |
|
18 |
{% endblock %} |
|
19 | ||
20 | ||
21 |
{% block sidebar %} |
|
22 |
<ul class="roles"> |
|
23 |
{% for role in roles %} |
|
24 |
<li> |
|
25 |
<a href="{% url "a2-manager-role" role_ref=role.ref %}" |
|
26 |
{% if role.ref == active_role.ref %}class="active"{% endif %}> |
|
27 |
{{ role.name }} |
|
28 |
</a> |
|
29 |
</li> |
|
30 |
{% endfor %} |
|
31 |
</ul> |
|
32 | ||
33 |
{% if active_role %} |
|
34 |
<hr /> |
|
35 |
<div id="search-form"> |
|
36 |
<h3>{% trans "Search" %}</h3> |
|
37 |
<input id="search-input" type="search" value="{{ request.GET.search }}"> |
|
38 |
<button>{% trans "Search" %}</button> |
|
39 |
</div> |
|
40 |
{% endif %} |
|
41 |
{% endblock %} |
|
42 | ||
43 |
{% block main %} |
|
44 |
<div class="big-msg-info"> |
|
45 |
Utilisez les filtres sur sur la gauche de l'écran pour afficher |
|
46 |
les membres du rôle correspondant. |
|
47 |
</div> |
|
48 |
{% endblock %} |
|
49 | ||
50 |
{% block page-end %} |
|
51 |
<div id="role-edit" style="display: none;"> |
|
52 |
<form> |
|
53 |
<label><span>Nom :</span> <input type="text" size="30" value="Foo"/></label></br> |
|
54 |
</form> |
|
55 |
</div> |
|
56 | ||
57 |
<div id="role-add" style="display: none;"> |
|
58 |
<form id="role-add-form" method="post" action="{% url "a2-manager-role-add" %}"> |
|
59 |
{% csrf_token %} |
|
60 |
{{ role_add_form }} |
|
61 |
</form> |
|
62 |
</div> |
|
63 | ||
64 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/table.html | ||
---|---|---|
1 |
{% extends "django_tables2/table.html" %} |
|
2 | ||
3 |
{% load django_tables2 %} |
|
4 | ||
5 |
{% block table.thead %} |
|
6 |
<thead> |
|
7 |
<tr> |
|
8 |
{% for column in table.columns %} |
|
9 |
{% if column.orderable %} |
|
10 |
<th {{ column.attrs.th.as_html }}><a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a></th> |
|
11 |
{% else %} |
|
12 |
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th> |
|
13 |
{% endif %} |
|
14 |
{% endfor %} |
|
15 |
{% block table.head.last.column %} |
|
16 |
{% endblock %} |
|
17 |
</tr> |
|
18 |
</thead> |
|
19 |
{% endblock table.thead %} |
|
20 | ||
21 |
{% block table.tbody.row %} |
|
22 |
<tr data-ref="{{ row.record.id }}" class="{{ forloop.counter|divisibleby:2|yesno:"even,odd" }}"> {# avoid cycle for Django 1.2-1.6 compatibility #} |
|
23 |
{% for column, cell in row.items %} |
|
24 |
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td> |
|
25 |
{% endfor %} |
|
26 |
{% block table.tbody.last.column %} |
|
27 |
{% endblock %} |
|
28 |
</tr> |
|
29 |
{% endblock table.tbody.row %} |
|
30 | ||
31 |
{% block pagination %} |
|
32 |
<p class="paginator"> |
|
33 |
{% if table.page.number > 1 %} |
|
34 |
{% if table.page.previous_page_number != 1 %} |
|
35 |
<a href="{% querystring table.prefixed_page_field=1 %}">1</a> |
|
36 |
... |
|
37 |
{% endif %} |
|
38 |
{% endif %} |
|
39 | ||
40 |
{% if table.page.has_previous %} |
|
41 |
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">{{ table.page.previous_page_number }}</a> |
|
42 |
{% endif %} |
|
43 | ||
44 |
<span class="this-page">{{ table.page.number }}</span> |
|
45 | ||
46 |
{% if table.page.has_next %} |
|
47 |
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">{{ table.page.next_page_number }}</a> |
|
48 |
{% endif %} |
|
49 |
{% if table.page.number != table.page.paginator.num_pages %} |
|
50 |
{% if table.page.paginator.num_pages > 1 %} |
|
51 |
{% if table.page.next_page_number != table.page.paginator.num_pages %} |
|
52 |
... |
|
53 |
<a href="{% querystring table.prefixed_page_field=table.page.paginator.num_pages %}">{{ table.page.paginator.num_pages }}</a> |
|
54 |
{% endif %} |
|
55 |
{% endif %} |
|
56 |
{% endif %} |
|
57 |
</p> |
|
58 |
{% endblock %} |
authentic2/manager/templates/authentic2/manager/users.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/sidebar.html" %} |
|
2 |
{% load i18n staticfiles django_tables2 %} |
|
3 | ||
4 |
{% block page-title %}{{ block.super }} - {% trans "Users management" %}{% endblock %} |
|
5 | ||
6 |
{% block page_title %}{% trans "Users management" %}{% endblock %} |
|
7 | ||
8 |
{% block appbar %} |
|
9 |
{{ block.super }} |
|
10 |
<a rel="popup" href="{% url "a2-manager-user-add" %}" id="add-user-btn">{% trans "Add user" %}</a> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block sidebar %} |
|
14 |
<div> |
|
15 |
<h3>{% trans "Search" %}</h3> |
|
16 |
<div id="search-form"> |
|
17 |
<input id="search-input" type="search" value="{{ request.GET.search }}"> |
|
18 |
<button>{% trans "Search" %}</button> |
|
19 |
</div> |
|
20 |
</div> |
|
21 |
<p>{{ users.count }} users</p> |
|
22 |
{% endblock %} |
|
23 | ||
24 |
{% block main %} |
|
25 |
{% render_table table "authentic2/manager/table.html" %} |
|
26 |
{% endblock %} |
authentic2/manager/urls.py | ||
---|---|---|
1 |
from django.conf.urls import patterns, url, include |
|
2 | ||
3 |
from . import views |
|
4 | ||
5 |
urlpatterns = patterns('authentic2.views', |
|
6 |
url(r'^$', views.homepage, name='a2-manager-homepage'), |
|
7 |
url(r'^roles/$', views.roles, name='a2-manager-roles'), |
|
8 |
url(r'^roles/add/$', views.role_add, |
|
9 |
name='a2-manager-role-add'), |
|
10 |
url(r'^roles/(?P<role_ref>[^/]*)/$', views.role, |
|
11 |
name='a2-manager-role'), |
|
12 |
url(r'^roles/(?P<role_ref>[^/]*)/edit/$', views.role_edit, |
|
13 |
name='a2-manager-role-edit'), |
|
14 |
url(r'^roles/(?P<role_ref>[^/]*)/delete/$', |
|
15 |
views.role_delete, name='a2-manager-role-delete'), |
|
16 |
url(r'^users/$', views.users, name='a2-manager-users'), |
|
17 |
url(r'^users/add/$', views.user_add, |
|
18 |
name='a2-manager-user-add'), |
|
19 |
url(r'^users/(?P<pk>[^/]*)/$', views.user_edit, |
|
20 |
name='a2-manager-user-edit'), |
|
21 | ||
22 |
url(r'^', include('django_select2.urls')), |
|
23 |
) |
authentic2/manager/utils.py | ||
---|---|---|
1 |
from django.contrib.auth.models import Group, User |
|
2 |
from django.db.models.query import Q |
|
3 | ||
4 | ||
5 |
class Role(object): |
|
6 |
def __init__(self, name, ref): |
|
7 |
self.name = name |
|
8 |
self.ref = ref |
|
9 | ||
10 |
class RoleUser(Role): |
|
11 |
pass |
|
12 | ||
13 | ||
14 |
def get_roles(): |
|
15 |
return [Role(g.name, g.id) for g in Group.objects.order_by('name')] |
|
16 | ||
17 |
def get_role(ref): |
|
18 |
g = Group.objects.get(id=ref) |
|
19 |
return Role(g.name, g.id) |
|
20 | ||
21 |
def filter_user(qs, search): |
|
22 |
return qs.filter(Q(username__icontains=search) |
|
23 |
| Q(first_name__icontains=search) |
|
24 |
| Q(last_name__icontains=search) |
|
25 |
| Q(email__icontains=search)) |
|
26 | ||
27 |
def get_role_users(role, search=None): |
|
28 |
qs = User.objects.filter(groups__id=role.ref) |
|
29 |
if search: |
|
30 |
qs = filter_user(qs, search) |
|
31 |
return qs |
|
32 | ||
33 |
def get_users(search=None): |
|
34 |
qs = User.objects.order_by('username') |
|
35 |
if search: |
|
36 |
qs = filter_user(qs, search) |
|
37 |
return qs |
|
38 | ||
39 |
def role_add(name): |
|
40 |
g, created = Group.objects.get_or_create(name=name) |
|
41 |
return g.id |
|
42 | ||
43 |
def search_user(term): |
|
44 |
return [RoleUser(u.get_full_name().strip() or u.username, u.id) for u in filter_user(User.objects.all(), term)[:10]] |
|
45 | ||
46 |
def add_user_to_role(role, user): |
|
47 |
u = User.objects.get(id=user) |
|
48 |
if u.groups.filter(id=role.ref).exists(): |
|
49 |
return False |
|
50 |
else: |
|
51 |
u.groups.add(Group.objects.get(id=role.ref)) |
|
52 |
return True |
|
53 | ||
54 |
def remove_user_from_role(role, user): |
|
55 |
User.objects.get(id=user).groups.remove(Group.objects.get(id=role.ref)) |
|
56 | ||
57 |
def delete_role(role): |
|
58 |
Group.objects.filter(id=role.ref).delete() |
authentic2/manager/views.py | ||
---|---|---|
1 |
import json |
|
2 | ||
3 |
from django.views.generic import (TemplateView, FormView, UpdateView, |
|
4 |
CreateView, DeleteView) |
|
5 |
from django.http import HttpResponse, HttpResponseRedirect |
|
6 |
from django.shortcuts import redirect |
|
7 |
from django.utils.translation import ugettext_lazy as _ |
|
8 |
from django.forms import models as model_forms |
|
9 |
from django.core.urlresolvers import reverse |
|
10 | ||
11 |
from django.contrib.auth.models import Group |
|
12 |
from django.contrib.auth.forms import PasswordResetForm |
|
13 |
from django.contrib.auth.tokens import default_token_generator |
|
14 |
from django.contrib.auth.decorators import (permission_required, |
|
15 |
login_required) |
|
16 | ||
17 |
from django.contrib import messages |
|
18 | ||
19 |
from django_tables2 import RequestConfig |
|
20 | ||
21 |
from authentic2.compat import get_user_model |
|
22 | ||
23 |
from . import app_settings, utils, tables, forms |
|
24 | ||
25 |
class Action(object): |
|
26 |
def __init__(self, name, title, confirm=None): |
|
27 |
self.name = name |
|
28 |
self.title = title |
|
29 |
self.confirm = confirm |
|
30 | ||
31 |
class ManagerMixin(object): |
|
32 |
def get_context_data(self, **kwargs): |
|
33 |
ctx = super(ManagerMixin, self).get_context_data(**kwargs) |
|
34 |
ctx['management_homepage_url'] = app_settings.HOMEPAGE_URL or reverse('auth_homepage') |
|
35 |
ctx['management_logout_url'] = app_settings.LOGOUT_URL or reverse('auth_logout') |
|
36 |
return ctx |
|
37 | ||
38 |
class RolesMixin(ManagerMixin): |
|
39 |
def get_context_data(self, **kwargs): |
|
40 |
ctx = super(ManagerMixin, self).get_context_data(**kwargs) |
|
41 |
ctx['roles'] = utils.get_roles() |
|
42 |
ctx['role_add_form'] = forms.RoleAddForm() |
|
43 |
return ctx |
|
44 | ||
45 |
class AjaxFormViewMixin(object): |
|
46 |
success_url = '.' |
|
47 | ||
48 |
def form_valid(self, form): |
|
49 |
if hasattr(form, 'save'): |
|
50 |
self.form_result = form.save() |
|
51 |
return super(AjaxFormViewMixin, self).form_valid(form) |
|
52 | ||
53 |
def dispatch(self, request, *args, **kwargs): |
|
54 |
response = super(AjaxFormViewMixin, self).dispatch(request, *args, **kwargs) |
|
55 |
if not request.is_ajax(): |
|
56 |
return response |
|
57 |
data = {} |
|
58 |
if 'Location' in response: |
|
59 |
data['location'] = response['Location'] |
|
60 |
if hasattr(response, 'render'): |
|
61 |
response.render() |
|
62 |
data['content'] = response.content |
|
63 |
return HttpResponse(json.dumps(data), content_type='application/json') |
|
64 | ||
65 |
class RolesView(RolesMixin, TemplateView): |
|
66 |
template_name = 'authentic2/manager/roles.html' |
|
67 | ||
68 |
class TitleMixin(object): |
|
69 |
title = None |
|
70 | ||
71 |
def get_context_data(self, **kwargs): |
|
72 |
ctx = super(TitleMixin, self).get_context_data(**kwargs) |
|
73 |
if self.title: |
|
74 |
ctx['title'] = self.title |
|
75 |
return ctx |
|
76 | ||
77 |
class ActionMixin(object): |
|
78 |
action = None |
|
79 | ||
80 |
def get_context_data(self, **kwargs): |
|
81 |
ctx = super(ActionMixin, self).get_context_data(**kwargs) |
|
82 |
if self.action: |
|
83 |
ctx['action'] = self.action |
|
84 |
return ctx |
|
85 | ||
86 |
class OtherActionsMixin(object): |
|
87 |
other_actions = None |
|
88 | ||
89 |
def get_context_data(self, **kwargs): |
|
90 |
ctx = super(OtherActionsMixin, self).get_context_data(**kwargs) |
|
91 |
ctx['other_actions'] = tuple(self.get_other_actions()) |
|
92 |
return ctx |
|
93 | ||
94 |
def get_other_actions(self): |
|
95 |
return self.other_actions or () |
|
96 | ||
97 |
def post(self, request, *args, **kwargs): |
|
98 |
self.object = self.get_object() |
|
99 |
for action in self.get_other_actions(): |
|
100 |
if action.name in request.POST: |
|
101 |
method = getattr(self, 'action_' + action.name, None) |
|
102 |
if method: |
|
103 |
response = method(request, *args, **kwargs) |
|
104 |
if response: |
|
105 |
return response |
|
106 |
self.request.method = 'GET' |
|
107 |
return self.get(request, *args, **kwargs) |
|
108 |
return super(OtherActionsMixin, self).post(request, *args, **kwargs) |
|
109 | ||
110 | ||
111 |
class RoleAddView(TitleMixin, AjaxFormViewMixin, FormView): |
|
112 |
template_name = 'authentic2/manager/form.html' |
|
113 |
form_class = forms.RoleAddForm |
|
114 |
title = _('Add new role') |
|
115 | ||
116 |
def form_valid(self, form): |
|
117 |
super(RoleAddView, self).form_valid(form) |
|
118 |
return redirect('a2-manager-role', role_ref=self.form_result) |
|
119 | ||
120 |
class RoleDeleteView(TitleMixin, AjaxFormViewMixin, DeleteView): |
|
121 |
template_name = 'authentic2/manager/delete.html' |
|
122 |
model = Group |
|
123 |
title = _('Delete role') |
|
124 |
pk_url_kwarg = 'role_ref' |
|
125 |
success_url = '..' |
|
126 | ||
127 | ||
128 |
class RoleEditView(TitleMixin, AjaxFormViewMixin, UpdateView): |
|
129 |
template_name = 'authentic2/manager/form.html' |
|
130 |
title = _('Edit role') |
|
131 |
model = Group |
|
132 |
pk_url_kwarg = 'role_ref' |
|
133 |
fields = ['name'] |
|
134 | ||
135 |
def get_form_class(self): |
|
136 |
return model_forms.modelform_factory(self.model, fields=self.fields) |
|
137 | ||
138 | ||
139 |
class RoleView(RolesMixin, TemplateView): |
|
140 |
template_name = 'authentic2/manager/role.html' |
|
141 | ||
142 |
def get_role(self): |
|
143 |
return utils.get_role(self.kwargs['role_ref']) |
|
144 | ||
145 |
def get_context_data(self, **kwargs): |
|
146 |
ctx = super(RoleView, self).get_context_data(**kwargs) |
|
147 |
ctx['active_role'] = self.get_role() |
|
148 |
kwargs = {} |
|
149 |
if 'search' in self.request.GET: |
|
150 |
kwargs = {'search': self.request.GET['search']} |
|
151 |
users = utils.get_role_users(ctx['active_role'], **kwargs) |
|
152 |
table = tables.UserTable(users) |
|
153 |
RequestConfig(self.request).configure(table) |
|
154 |
ctx['users'] = table |
|
155 |
ctx['choose_user_form'] = forms.ChooseUserForm() |
|
156 |
return ctx |
|
157 | ||
158 |
def post(self, request, *args, **kwargs): |
|
159 |
role = self.get_role() |
|
160 |
ref = request.POST.get('ref') |
|
161 |
if ref: |
|
162 |
action = request.POST.get('action', 'add') |
|
163 |
if action == 'add': |
|
164 |
if not utils.add_user_to_role(role, ref): |
|
165 |
messages.warning(request, _('User already in ' |
|
166 |
'this role')) |
|
167 |
elif action == 'remove': |
|
168 |
utils.remove_user_from_role(role, ref) |
|
169 |
if 'delete' in request.GET: |
|
170 |
utils.delete_role(role) |
|
171 |
return HttpResponseRedirect('..') |
|
172 |
return HttpResponseRedirect('') |
|
173 | ||
174 | ||
175 | ||
176 |
roles = permission_required('group.add', raise_exception=True)(RolesView.as_view()) |
|
177 |
role_add = permission_required('group.add', raise_exception=True)(RoleAddView.as_view()) |
|
178 |
role_edit = permission_required('group.change', raise_exception=True)(RoleEditView.as_view()) |
|
179 |
role_delete = permission_required('group.delete', raise_exception=True)(RoleDeleteView.as_view()) |
|
180 |
role = permission_required('group.delete', raise_exception=True)(RoleView.as_view()) |
|
181 | ||
182 |
class UsersView(RolesMixin, TemplateView): |
|
183 |
template_name = 'authentic2/manager/users.html' |
|
184 | ||
185 |
def get_context_data(self, **kwargs): |
|
186 |
ctx = super(UsersView, self).get_context_data(**kwargs) |
|
187 |
if 'search' in self.request.GET: |
|
188 |
kwargs = {'search': self.request.GET['search']} |
|
189 |
users = utils.get_users(**kwargs) |
|
190 |
ctx['users'] = users |
|
191 |
table = tables.UserTable(users) |
|
192 |
RequestConfig(self.request).configure(table) |
|
193 |
ctx['table'] = table |
|
194 |
return ctx |
|
195 | ||
196 |
class UserMixin(object): |
|
197 |
model = get_user_model() |
|
198 |
template_name = 'authentic2/manager/form.html' |
|
199 |
fields = ['username', 'first_name', 'last_name', 'email', 'is_active'] |
|
200 |
form_class = forms.UserEditForm |
|
201 | ||
202 |
class UserAddView(UserMixin, ActionMixin, TitleMixin, |
|
203 |
AjaxFormViewMixin, CreateView): |
|
204 |
title = _('Create user') |
|
205 |
action = _('Create') |
|
206 | ||
207 |
class UserEditView(UserMixin, OtherActionsMixin, ActionMixin, TitleMixin, |
|
208 |
AjaxFormViewMixin, UpdateView): |
|
209 |
title = _('Edit user') |
|
210 |
action = _('Edit') |
|
211 |
fields = ['username', 'first_name', 'last_name', 'email'] |
|
212 | ||
213 |
def get_other_actions(self): |
|
214 |
yield Action('password_reset', _('Reset password')) |
|
215 |
if self.object.is_active: |
|
216 |
yield Action('deactivate', _('Deactivate')) |
|
217 |
else: |
|
218 |
yield Action('activate', _('Activate')) |
|
219 |
yield Action('delete', |
|
220 |
_('Delete'), |
|
221 |
_('Do you really want to delete "%s" ?') % self.object.username) |
|
222 | ||
223 |
def action_activate(self, request, *args, **kwargs): |
|
224 |
self.object.is_active = True |
|
225 |
self.object.save() |
|
226 | ||
227 |
def action_deactivate(self, request, *args, **kwargs): |
|
228 |
self.object.is_active = False |
|
229 |
self.object.save() |
|
230 | ||
231 |
def action_delete(self, request, *args, **kwargs): |
|
232 |
self.object.delete() |
|
233 |
return HttpResponseRedirect('.') |
|
234 | ||
235 |
def action_password_reset(self, request, *args, **kwargs): |
|
236 |
# FIXME: a bit hacky, could break if PasswordResetForm implementation changes |
|
237 |
# copied from django.contrib.auth.views and django.contrib.auth.forms |
|
238 |
form = PasswordResetForm() |
|
239 |
form.users_cache = [self.object] |
|
240 |
opts = { |
|
241 |
'use_https': request.is_secure(), |
|
242 |
'token_generator': default_token_generator, |
|
243 |
'request': request, |
|
244 |
} |
|
245 |
form.save(**opts) |
|
246 |
messages.info(request, _('A mail was sent to %s') % self.object.email) |
|
247 | ||
248 | ||
249 |
users = permission_required('user.delete', raise_exception=True)(UsersView.as_view()) |
|
250 |
user_add = permission_required('user.add', raise_exception=True)(UserAddView.as_view()) |
|
251 |
user_edit = permission_required('user.change', raise_exception=True)(UserEditView.as_view()) |
|
252 | ||
253 |
class HomepageView(ManagerMixin, TemplateView): |
|
254 |
template_name = 'authentic2/manager/homepage.html' |
|
255 | ||
256 |
homepage = login_required(HomepageView.as_view()) |
authentic2/settings.py | ||
---|---|---|
175 | 175 |
'admin_tools.dashboard', |
176 | 176 |
'django.contrib.admin', |
177 | 177 |
'registration', |
178 |
'django_select2', |
|
179 |
'django_tables2', |
|
178 | 180 |
'authentic2.nonce', |
179 | 181 |
'authentic2.saml', |
180 | 182 |
'authentic2.idp', |
... | ... | |
182 | 184 |
'authentic2.auth2_auth', |
183 | 185 |
'authentic2.attribute_aggregator', |
184 | 186 |
'authentic2.disco_service', |
187 |
'authentic2.manager', |
|
185 | 188 |
'authentic2', |
186 | 189 |
) |
187 | 190 |
authentic2/urls.py | ||
---|---|---|
23 | 23 |
url(r'^admin/', include(admin.site.urls)), |
24 | 24 |
url(r'^admin_tools/', include('admin_tools.urls')), |
25 | 25 |
url(r'^idp/', include('authentic2.idp.urls')), |
26 |
url(r'^manager/', include('authentic2.manager.urls')), |
|
26 | 27 |
) |
27 | 28 | |
28 | 29 |
if getattr(settings, 'AUTH_OPENID', False): |
requirements.txt | ||
---|---|---|
8 | 8 |
--allow-unverified django-admin-tools |
9 | 9 |
django-admin-tools>=0.5.1 |
10 | 10 |
dnspython |
11 |
django-select2 |
|
12 |
django-tables2 |
setup.py | ||
---|---|---|
120 | 120 |
'django-registration>=1', |
121 | 121 |
'django-admin-tools>=0.5.1', |
122 | 122 |
'django-debug-toolbar<1.0.0', |
123 |
'dnspython',], |
|
123 |
'dnspython', |
|
124 |
'django-select2', |
|
125 |
'django-tables2', |
|
126 |
], |
|
124 | 127 |
zip_safe=False, |
125 | 128 |
classifiers=[ |
126 | 129 |
"Development Status :: 5 - Production/Stable", |
127 |
- |