Edit users done. I'll update the form, because now we can add all fields to 1 single tab

This commit is contained in:
Adolfo Gómez 2013-12-09 02:10:55 +00:00
parent b3c6e46f0b
commit db29667f4c
11 changed files with 248 additions and 33 deletions

View File

@ -63,6 +63,15 @@ class Authenticators(ModelHandler):
def enum_types(self):
return auths.factory().providers().values()
def typeInfo(self, type_):
return {
'canSearchUsers' : type_.searchUsers != auths.Authenticator.searchUsers,
'canSearchGroups' : type_.searchGroups != auths.Authenticator.searchGroups,
'needsPassword' : type_.needsPassword, 'userNameLabel' : _(type_.userNameLabel),
'groupNameLabel' : _(type_.groupNameLabel), 'passwordLabel' : _(type_.passwordLabel),
'canCreateUsers' : type_.createUser != auths.Authenticator.createUser,
}
def getGui(self, type_):
try:
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority', 'small_name'])

View File

@ -35,11 +35,12 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.forms.models import model_to_dict
from uds.core.util.State import State
from django.db import IntegrityError
from uds.core.util import log
from uds.models import Authenticator
from uds.models import Authenticator, User, Group
from uds.REST import RequestError
from uds.REST.handlers import HandlerError
from uds.REST.model import DetailHandler
import logging
@ -88,6 +89,38 @@ class Users(DetailHandler):
return log.getLogs(user)
def saveItem(self, parent, item):
# Extract item db fields
# We need this fields for all
logger.debug('Saving user {0} / {1}'.format(parent, item))
fields = self.readFieldsFromParams(['name', 'real_name', 'comments', 'state', 'staff_member', 'is_admin', 'groups'])
try:
auth = parent.getInstance()
groups = fields['groups']
del fields['groups'] # Not update this on user dict
if item is None: # Create new
auth.createUser(fields) # this throws an exception if there is an error (for example, this auth can't create users)
user = parent.users.create(**fields)
else:
auth.modifyUser(fields) # Notifies authenticator
user = parent.users.get(pk=item)
user.__dict__.update(fields)
if auth.isExternalSource == False and user.parent == -1:
user.groups = Group.objects.filter(id__in=groups)
user.save()
except User.DoesNotExist:
self.invalidItemException()
except IntegrityError: # Duplicate key probably
raise RequestError(_('User already exists (duplicate key error)'))
except Exception:
logger.exception('Saving Service')
self.invalidRequestException()
return self.getItems(parent, user.id)
class Groups(DetailHandler):
def getItems(self, parent, item):

View File

@ -111,12 +111,17 @@ class BaseModelHandler(Handler):
return gui
def typeInfo(self, type_):
return {}
def type_as_dict(self, type_):
return { 'name' : _(type_.name()),
res = self.typeInfo(type_)
res.update( { 'name' : _(type_.name()),
'type' : type_.type(),
'description' : _(type_.description()),
'icon' : type_.icon().replace('\n', '')
}
})
return res
def processTableFields(self, title, fields):
# processedFields = [{ 'id' : {'visible': False, 'sortable': False, 'searchable': False } }]

View File

@ -17,6 +17,18 @@
return options.inverse(this);
}
});
// Belongs comparision (similar to "if xxx in yyyyy")
// Use as block as {{#ifbelong [element] [group]}}....{{/ifbelongs}}
Handlebars.registerHelper('ifbelongs', function(context1, context2, options) {
gui.doLog('belongs', context1, context2);
if($.inArray(context1, context2) != -1) {
gui.doLog('belongs is true');
return options.fn(this);
} else {
return options.inverse(this);
}
});
// Counters.
// Create a counter with {{counter [id] [startValue]}}
@ -61,7 +73,7 @@
success_fnc = success_fnc || function(){};
api.doLog('Getting template ' + name);
if (name.indexOf('?') == -1) {
if ($this.cache.get(name) ) {
if ($this.cache.get(name+'-------') ) {
success_fnc($this.cache.get(name));
return;
// Let's check if a "preloaded template" exists

View File

@ -127,7 +127,7 @@
// Public attributes
api.debug = false;
api.debug = true;
}(window.api = window.api || {}, jQuery));

View File

@ -12,6 +12,8 @@ gui.authenticators.link = function(event) {
},
};
// Clears the log of the detail, in this case, the log of "users"
// Memory saver :-)
var detailLogTable;
var clearDetailLog = function() {
if( detailLogTable ) {
@ -23,6 +25,8 @@ gui.authenticators.link = function(event) {
}
};
// Clears the details
// Memory saver :-)
var prevTables = [];
var clearDetails = function() {
$.each(prevTables, function(undefined, tbl){
@ -42,7 +46,6 @@ gui.authenticators.link = function(event) {
prevTables = [];
};
gui.doLog('enter auths');
api.templates.get('authenticators', function(tmpl) {
gui.clearWorkspace();
gui.appendToWorkspace(api.templates.evaluate(tmpl, {
@ -71,6 +74,9 @@ gui.authenticators.link = function(event) {
gui.tools.blockUI();
var id = selected[0].id;
var type = gui.authenticators.types[selected[0].type];
gui.doLog('Type', type);
var user = new GuiElement(api.authenticators.detail(id, 'users'), 'users');
var group = new GuiElement(api.authenticators.detail(id, 'groups'), 'groups');
var grpTable = group.table({
@ -82,7 +88,13 @@ gui.authenticators.link = function(event) {
},
});
var tmpLogTable;
// Use defered rendering for users, this table can be "huge"
// New button will only be shown on authenticators that can create new users
var usrButtons = ['edit', 'delete', 'xls'];
if( type.canCreateUsers ) {
usrButtons = ['new'].concat(usrButtons); // New is first button
}
var usrTable = user.table({
container : 'users-placeholder',
rowSelect : 'single',
@ -103,12 +115,64 @@ gui.authenticators.link = function(event) {
onRowDeselect : function() {
clearDetailLog();
},
buttons : [ 'new', 'edit', 'delete', 'xls' ],
deferedRender: true,
buttons : usrButtons,
deferedRender: true, // Use defered rendering for users, this table can be "huge"
scrollToTable : false,
onLoad: function(k) {
gui.tools.unblockUI();
},
onEdit: function(value, event, table, refreshFnc) {
var password = "#æð~¬~@æß”¢ß€~½¬@#~½¬@|"; // Garbage for password (to detect change)
// Gets fields gui
gui.tools.blockUI();
api.templates.get('user', function(tmpl) { // Get form template
group.rest.overview(function(groups) { // Get groups
user.rest.item(value.id, function(item){ // Get item to edit
// Creates modal
var modalId = gui.launchModal(gettext('Edit user'), api.templates.evaluate(tmpl, {
id: item.id,
username: item.name,
username_label: type.userNameLabel,
realname: item.real_name,
comments: item.comments,
state: item.state,
staff_member: item.staff_member,
is_admin: item.is_admin,
password: type.needsPassword ? password : undefined,
password_label: type.passwordLabel,
groups_all: groups,
groups: item.groups,
}));
// Activate "custom" styles
$(modalId + ' .make-switch').bootstrapSwitch();
// Activate "cool" selects
$(modalId + ' .selectpicker').selectpicker();
// TEST: cooler on mobile devices
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
$(modalId + ' .selectpicker').selectpicker('mobile');
}
// Activate tooltips
$(modalId + ' [data-toggle="tooltip"]').tooltip({delay: {show: 1000, hide: 100}, placement: 'auto right'});
gui.tools.unblockUI();
$(modalId + ' .button-accept').click(function(){
var fields = gui.forms.read(modalId);
gui.doLog('Fields', fields);
user.rest.save(fields, function(data) { // Success on put
$(modalId).modal('hide');
refreshFnc();
gui.notify(gettext('User saved'), 'success');
}, gui.failRequestModalFnc("Error saving user", true));
});
});
});
});
},
onNew: function(type, table, refreshFnc) {
}
});
var logTable = gui.authenticators.logTable(id, {

View File

@ -21,28 +21,25 @@ GuiElement.prototype = {
gui.doLog('Initializing ' + this.name);
var self = this;
this.rest.types(function(data) {
var styles = '';
var alreadyAttached = $('#gui-style-'+self.name).length !== 0;
$.each(data, function(index, value) {
var className = self.name + '-' + value.type;
self.types[value.type] = {
css : className,
name : value.name || '',
description : value.description || ''
};
gui.doLog('Creating style for ' + className);
if( !alreadyAttached ) {
var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
styles += style;
}
});
if (styles !== '') {
// If style already attached, do not re-attach it
styles = '<style id="gui-style-' + self.name + '" media="screen">' + styles + '</style>';
$(styles).appendTo('head');
var styles = '';
var alreadyAttached = $('#gui-style-'+self.name).length !== 0;
$.each(data, function(index, value) {
var className = self.name + '-' + value.type;
self.types[value.type] = value;
self.types[value.type].css = className;
gui.doLog('Creating style for ' + className);
if( !alreadyAttached ) {
var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
styles += style;
}
});
if (styles !== '') {
// If style already attached, do not re-attach it
styles = '<style id="gui-style-' + self.name + '" media="screen">' + styles + '</style>';
$(styles).appendTo('head');
}
});
},
// Options: dictionary

View File

@ -24,6 +24,9 @@
var originalValues = {}; // Initial stored values (defaults to "reset" form and also used on fillers callback to try to restore previous value)
// itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted)
$.each(itemGui, function(index, f){
if( f.gui === undefined ) { // Not exactly a field, maybe some other info...
return;
}
// Fix multiline text fields to textbox
if( f.gui.type == 'text' && f.gui.multiline ) {
f.gui.type = 'textbox';

View File

@ -1,11 +1,11 @@
{% extends "uds/admin/tmpl/fld/form-group.html" %}
{% load i18n %}
{% comment %}The choice item MUST be a Select{% endcomment %}
{% comment %}The choice item MUST be a Select.{% endcomment %}
{% block field %}
{% verbatim %}
<select class="selectpicker show-menu-arrow {{ css }}" multiple data-style="btn-default" data-selected-text-format="count>3" data-width="100%" name="{{ name }}" id="{{ name }}_field" {{# if readonly }} disabled{{/ if }}>
{{#each values }}
<option value="{{ id }}"{{# ifequals id ../value }}selected{{/ ifequals }}>{{ text }}</option>
<option value="{{ id }}"{{# ifbelongs id ../value }}selected{{/ ifbelongs }}>{{ text }}</option>
{{/each}}
</select>
{% endverbatim %}

View File

@ -0,0 +1,93 @@
{% load i18n %}
{% verbatim %}
<form id="user_form" class="form-horizontal" role="form">
<ul class="nav nav-tabs">
<li class="active"><a href="#id_user_pane" data-toggle="tab">{% endverbatim %}{% trans 'User' %}{% verbatim %}</a></li>
<li><a href="#id_groups_pane" data-toggle="tab">{% endverbatim %}{% trans 'Groups' %}{% verbatim %}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active fade in" id="id_user_pane">
<input type="hidden" name="id" value="{{ id }}" class="modal_field_data">
<div class="form-group">
<label for="id_username" class="col-sm-2 control-label">{{ username_label }}</label>
<div class="col-sm-10">
<input name="name" value="{{ username }}" type="text" id="id_username" class="form-control modal_field_data"
placeholder="{% endverbatim %}{% trans 'Username' %}{% verbatim %}"{{# if username }}readonly{{/ if }}>
</div>
</div>
<div class="form-group">
<label for="id_realname" class="col-sm-2 control-label">{% endverbatim %}{% trans 'Name' %}{% verbatim %}</label>
<div class="col-sm-10">
<input name="real_name" value="{{ realname }}" type="text" id="id_realname" class="form-control modal_field_data"
placeholder="{% endverbatim %}{% trans 'Name' %}{% verbatim %}">
</div>
</div>
<div class="form-group">
<label for="id_comments" class="col-sm-2 control-label">{% endverbatim %}{% trans 'comments' %}{% verbatim %}</label>
<div class="col-sm-10">
<input name="comments" value="{{ comments }}" type="text" id="id_comments" class="form-control modal_field_data"
placeholder="{% endverbatim %}{% trans 'Comments' %}{% verbatim %}">
</div>
</div>
<div class="form-group">
<label for="id_state" class="col-sm-2 control-label">{% endverbatim %}{% trans 'State' %}{% verbatim %}</label>
<div class="col-sm-10">
<select name="state" class="selectpicker show-menu-arrow show-tick modal_field_data" data-style="btn-default" data-width="100%" id="id_state">
<option value="A"{{# ifequals state 'A'}} selected{{/ ifequals }}>{% endverbatim %}{% trans 'Enabled' %}{% verbatim %}</option>
<option value="I"{{# ifequals state 'I'}} selected{{/ ifequals }}>{% endverbatim %}{% trans 'Disabled' %}{% verbatim %}</option>
<option value="B"{{# ifequals state 'B'}} selected{{/ ifequals }}>{% endverbatim %}{% trans 'Blocked' %}{% verbatim %}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="id_staffmember" class="col-sm-2 control-label">{% endverbatim %}{% trans 'Staff member' %}{% verbatim %}</label>
<div class="col-sm-10">
<div class="make-switch" data-on-label="{% endverbatim %}{% trans 'Yes' %}{% verbatim %}"
data-off-label="{% endverbatim %}{% trans 'No' %}{% verbatim %}">
<input type="checkbox" class="modal_field_data" name="staff_member" id="id_staffmember"{{# if staff_member }} checked{{/ if }}>
</div>
</div>
</div>
<div class="form-group">
<label for="id_admin" class="col-sm-2 control-label">{% endverbatim %}{% trans 'Admin' %}{% verbatim %}</label>
<div class="col-sm-10">
<div class="make-switch" data-on-label="{% endverbatim %}{% trans 'Yes' %}{% verbatim %}" data-off-label="{% endverbatim %}{% trans 'No' %}{% verbatim %}">
<input type="checkbox" class="modal_field_data" name="is_admin" id="id_admin"{{# if is_admin }} checked{{/ if }}>
</div>
</div>
</div>
{{# if password }}
<div class="form-group">
<label for="id_password" class="col-sm-2 control-label">{{ password_label }}</label>
<div class="col-sm-10">
<input name="password" value="{{ password }}" type="password" id="id_password" class="form-control modal_field_data"
placeholder="{% endverbatim %}{% trans 'Password' %}{% verbatim %}">
</div>
</div>
{{/ if }}
</div>
<div class="tab-pane fade" id="id_groups_pane">
<div class="form-group">
<label for="id_password" class="col-sm-2 control-label">{% endverbatim %}{% trans 'Groups' %}{% verbatim %}</label>
<div class="col-sm-10">
<select class="selectpicker show-menu-arrow modal_field_data" multiple data-style="btn-default" countSelectedText="{% endverbatim %}{% trans '{0} of {1} selected' %}{% verbatim %}"
data-selected-text-format="count>3" data-width="100%" name="groups" id="id_groups">
{{#each groups_all }}
<option value="{{ id }}"{{# ifbelongs id ../groups }}selected{{/ ifbelongs }}>{{ name }}</option>
{{/each}}
</select>
</div>
</div>
</div>
</form>
{% endverbatim %}

View File

@ -50,7 +50,6 @@ def dictFromUser(usr, groups = None):
'staffMember' : usr.staff_member, 'isAdmin' : usr.is_admin }
if groups != None:
dct['groups'] = groups
logger.debug('Dict: {0}'.format(dct))
return dct
@needs_credentials