forked from shaba/openuds
* Finished create/edit methods
* Added delete method * a little more refactoring ( ofc :-) ) Need to debug a bit more this, and probably make some adaptions for models that do not serializes objects
This commit is contained in:
parent
63da672f30
commit
fa2335efd4
@ -34,6 +34,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from handlers import NotFound, RequestError
|
from handlers import NotFound, RequestError
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.db import IntegrityError
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -66,8 +67,14 @@ class ModelHandlerMixin(object):
|
|||||||
detail = None # Dictionary containing detail routing
|
detail = None # Dictionary containing detail routing
|
||||||
model = None
|
model = None
|
||||||
save_fields = []
|
save_fields = []
|
||||||
|
|
||||||
|
|
||||||
|
def __fillIntanceFields(self, item, res):
|
||||||
|
if hasattr(item, 'getInstance'):
|
||||||
|
for key, value in item.getInstance().valuesDict().iteritems():
|
||||||
|
value = {"true":True, "false":False}.get(value, value)
|
||||||
|
logger.debug('{0} = {1}'.format(key, value))
|
||||||
|
res[key] = value
|
||||||
|
|
||||||
def item_as_dict(self, item):
|
def item_as_dict(self, item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -96,9 +103,7 @@ class ModelHandlerMixin(object):
|
|||||||
result = []
|
result = []
|
||||||
for val in self.model.objects.all():
|
for val in self.model.objects.all():
|
||||||
res = self.item_as_dict(val)
|
res = self.item_as_dict(val)
|
||||||
if hasattr(val, 'getInstance'):
|
self.__fillIntanceFields(val, res)
|
||||||
for key, value in val.getInstance().valuesDict().iteritems():
|
|
||||||
res[key] = value
|
|
||||||
result.append(res)
|
result.append(res)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -109,14 +114,11 @@ class ModelHandlerMixin(object):
|
|||||||
if self.detail is not None and len(self._args) > 1:
|
if self.detail is not None and len(self._args) > 1:
|
||||||
return self.processDetail()
|
return self.processDetail()
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
val = self.model.objects.get(pk=self._args[0])
|
val = self.model.objects.get(pk=self._args[0])
|
||||||
res = self.item_as_dict(val)
|
res = self.item_as_dict(val)
|
||||||
if hasattr(val, 'getInstance'):
|
self.__fillIntanceFields(val, res)
|
||||||
for key, value in val.getInstance().valuesDict().iteritems():
|
return res
|
||||||
res[key] = value
|
|
||||||
return res
|
|
||||||
except:
|
except:
|
||||||
raise NotFound('item not found')
|
raise NotFound('item not found')
|
||||||
|
|
||||||
@ -129,46 +131,49 @@ class ModelHandlerMixin(object):
|
|||||||
del self._params[key]
|
del self._params[key]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise RequestError('needed parameter not found in data {0}'.format(unicode(e)))
|
raise RequestError('needed parameter not found in data {0}'.format(unicode(e)))
|
||||||
|
|
||||||
if len(args) == 0: # create new
|
|
||||||
isNew = False
|
|
||||||
try:
|
|
||||||
item = self.model.objects.create(**args);
|
|
||||||
res = self.item_as_dict(item)
|
|
||||||
except: # Duplicate key probably
|
|
||||||
raise RequestError('Element already exists (duplicate key error)')
|
|
||||||
|
|
||||||
elif len(args) == 1:
|
|
||||||
try:
|
|
||||||
item = self.model.objects.get(pk=self._args[0]);
|
|
||||||
# Update "general" values
|
|
||||||
item.update(**args)
|
|
||||||
res = self.item_as_dict(item)
|
|
||||||
except:
|
|
||||||
raise RequestError('Element {0} do not exists anymore'.format(self._args[0]))
|
|
||||||
else:
|
|
||||||
raise RequestError('incorrect invocation to PUT')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
isNew = True
|
if len(self._args) == 0: # create new
|
||||||
if self._params.has_key('data_type'): # Needs to store instance
|
item = self.model.objects.create(**args);
|
||||||
item.data_type = self._params['data_type']
|
elif len(self._args) == 1:
|
||||||
item.data = item.getInstance(self._params).serialize()
|
# We have to take care with this case, update will efectively update records on db
|
||||||
|
item = self.model.objects.get(pk=self._args[0]);
|
||||||
for key, value in item.getInstance().valuesDict().iteritems():
|
item.__dict__.update(args) # Update fields from args
|
||||||
res[key] = value
|
else:
|
||||||
|
raise Exception() # Incorrect invocation
|
||||||
item.save()
|
except self.model.DoesNotExist:
|
||||||
|
raise NotFound('Element do not exists')
|
||||||
|
except IntegrityError: # Duplicate key probably
|
||||||
|
raise RequestError('Element already exists (duplicate key error)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
item.delete() # Remove pre-saved element
|
raise RequestError('incorrect invocation to PUT')
|
||||||
raise RequestError(unicode(e))
|
|
||||||
|
# Store associated object if needed
|
||||||
|
if self._params.has_key('data_type'): # Needs to store instance
|
||||||
|
item.data_type = self._params['data_type']
|
||||||
|
item.data = item.getInstance(self._params).serialize()
|
||||||
|
|
||||||
|
res = self.item_as_dict(item)
|
||||||
|
|
||||||
|
self.__fillIntanceFields(item, res)
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
logger.debug('method DELETE for {0}, {1}'.format(self.__class__.__name__, self._args))
|
logger.debug('method DELETE for {0}, {1}'.format(self.__class__.__name__, self._args))
|
||||||
if len(self._args) != 1:
|
if len(self._args) != 1:
|
||||||
raise RequestError('Delete need an argument')
|
raise RequestError('Delete need one and only one argument')
|
||||||
|
try:
|
||||||
|
item = self.model.objects.get(pk=self._args[0]);
|
||||||
|
item.delete()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
raise NotFound('Element do not exists')
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception('delete')
|
||||||
|
raise RequestError('incorrect invocation to DELETE')
|
||||||
|
|
||||||
return 'deleted'
|
return 'deleted'
|
||||||
|
|
||||||
class ModelTypeHandlerMixin(object):
|
class ModelTypeHandlerMixin(object):
|
||||||
|
@ -96,7 +96,35 @@
|
|||||||
request.setRequestHeader(api.config.auth_header, api.config.token);
|
request.setRequestHeader(api.config.auth_header, api.config.token);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}; // End putJson
|
||||||
|
|
||||||
|
|
||||||
|
api.deleteJson = function(path, options) {
|
||||||
|
options = options || {};
|
||||||
|
var success_fnc = options.success || function(){};
|
||||||
|
var fail_fnc = options.fail || api.defaultFail;
|
||||||
|
|
||||||
|
var url = api.url_for(path);
|
||||||
|
api.doLog('Ajax DELETE Json for "' + url + '"');
|
||||||
|
$.ajax({
|
||||||
|
url : url,
|
||||||
|
type : "DELETE",
|
||||||
|
dataType : "json",
|
||||||
|
success: function(data) {
|
||||||
|
api.doLog('Success on DELETE "' + url + '".');
|
||||||
|
api.doLog('Received ', data);
|
||||||
|
success_fnc(data);
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
api.doLog('Error on DELETE "' + url + '". ', textStatus, ', ', errorThrown);
|
||||||
|
fail_fnc(jqXHR, textStatus, errorThrown);
|
||||||
|
},
|
||||||
|
beforeSend : function(request) {
|
||||||
|
request.setRequestHeader(api.config.auth_header, api.config.token);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}; // End putJson
|
||||||
|
|
||||||
|
|
||||||
// Public attributes
|
// Public attributes
|
||||||
api.debug = true;
|
api.debug = true;
|
||||||
@ -142,6 +170,7 @@ function BasicModelRest(path, options) {
|
|||||||
this.path = path;
|
this.path = path;
|
||||||
this.getPath = options.getPath || path;
|
this.getPath = options.getPath || path;
|
||||||
this.putPath = options.putPath || path;
|
this.putPath = options.putPath || path;
|
||||||
|
this.delPath = options.delPath || path;
|
||||||
this.typesPath = options.typesPath || (path + '/types');
|
this.typesPath = options.typesPath || (path + '/types');
|
||||||
this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo');
|
this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo');
|
||||||
this.cache = api.cache('bmr'+path);
|
this.cache = api.cache('bmr'+path);
|
||||||
@ -254,6 +283,20 @@ BasicModelRest.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Delete
|
||||||
|
// --------------
|
||||||
|
del: function(id, success_fnc, fail_fnc) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var path = this.delPath + '/' + id;
|
||||||
|
|
||||||
|
api.deleteJson(path, {
|
||||||
|
success: success_fnc,
|
||||||
|
fail: fail_fnc
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// --------------
|
// --------------
|
||||||
// Types methods
|
// Types methods
|
||||||
// --------------
|
// --------------
|
||||||
|
@ -139,15 +139,22 @@ gui.connectivity.link = function(event) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
gui.connectivity.transports.table({
|
gui.connectivity.transports.table({
|
||||||
rowSelect : 'multi',
|
rowSelect : 'single',
|
||||||
container : 'transports-placeholder',
|
container : 'transports-placeholder',
|
||||||
buttons : [ 'new', 'edit', 'delete', 'xls' ],
|
buttons : [ 'new', 'edit', 'delete', 'xls' ],
|
||||||
onEdit: function(value, event, table) {
|
onEdit: function(value, event, table, refreshFnc) {
|
||||||
gui.connectivity.transports.rest.gui(value.type, function(itemGui){
|
gui.connectivity.transports.rest.gui(value.type, function(itemGui){
|
||||||
gui.connectivity.transports.rest.item(value.id, function(item) {
|
gui.connectivity.transports.rest.item(value.id, function(item) {
|
||||||
var form = gui.fields(itemGui, item);
|
var form = gui.form.fromFields(itemGui, item);
|
||||||
gui.launchModalForm(gettext('Edit transport')+' '+value.name,form, function(form_selector) {
|
gui.launchModalForm(gettext('Edit transport')+' '+value.name,form, function(form_selector, closeFnc) {
|
||||||
var fields = gui.fields.read(form_selector);
|
var fields = gui.form.read(form_selector);
|
||||||
|
fields.data_type = value.type;
|
||||||
|
fields.nets_positive = false;
|
||||||
|
gui.connectivity.transports.rest.save(fields, function(data) { // Success on put
|
||||||
|
closeFnc();
|
||||||
|
refreshFnc();
|
||||||
|
}, gui.failRequestModalFnc(gettext('Error creating transport')) // Fail on put, show modal message
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -155,9 +162,9 @@ gui.connectivity.link = function(event) {
|
|||||||
},
|
},
|
||||||
onNew: function(type, table, refreshFnc) {
|
onNew: function(type, table, refreshFnc) {
|
||||||
gui.connectivity.transports.rest.gui(type, function(itemGui) {
|
gui.connectivity.transports.rest.gui(type, function(itemGui) {
|
||||||
var form = gui.fields(itemGui);
|
var form = gui.form.fromFields(itemGui);
|
||||||
gui.launchModalForm(gettext('New transport'), form, function(form_selector, closeFnc) {
|
gui.launchModalForm(gettext('New transport'), form, function(form_selector, closeFnc) {
|
||||||
var fields = gui.fields.read(form_selector);
|
var fields = gui.form.read(form_selector);
|
||||||
// Append "own" fields, in this case data_type
|
// Append "own" fields, in this case data_type
|
||||||
fields.data_type = type;
|
fields.data_type = type;
|
||||||
fields.nets_positive = false;
|
fields.nets_positive = false;
|
||||||
@ -169,6 +176,13 @@ gui.connectivity.link = function(event) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onDelete: function(value, event, table, refreshFnc) {
|
||||||
|
// TODO: Add confirmation to deletion
|
||||||
|
gui.connectivity.transports.rest.del(value.id, function(){
|
||||||
|
refreshFnc();
|
||||||
|
}, gui.failRequestModalFnc(gettext('Error removing transport'))
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
gui.connectivity.networks.table({
|
gui.connectivity.networks.table({
|
||||||
rowSelect : 'multi',
|
rowSelect : 'multi',
|
||||||
|
@ -19,12 +19,12 @@ GuiElement.prototype = {
|
|||||||
init : function() {
|
init : function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
gui.doLog('Initializing ' + this.name);
|
gui.doLog('Initializing ' + this.name);
|
||||||
var $this = this;
|
var self = this;
|
||||||
this.rest.types(function(data) {
|
this.rest.types(function(data) {
|
||||||
var styles = '';
|
var styles = '';
|
||||||
$.each(data, function(index, value) {
|
$.each(data, function(index, value) {
|
||||||
var className = $this.name + '-' + value.type;
|
var className = self.name + '-' + value.type;
|
||||||
$this.types[value.type] = {
|
self.types[value.type] = {
|
||||||
css : className,
|
css : className,
|
||||||
name : value.name || '',
|
name : value.name || '',
|
||||||
description : value.description || ''
|
description : value.description || ''
|
||||||
@ -78,7 +78,7 @@ GuiElement.prototype = {
|
|||||||
options = options || {};
|
options = options || {};
|
||||||
gui.doLog('Composing table for ' + this.name);
|
gui.doLog('Composing table for ' + this.name);
|
||||||
var tableId = this.name + '-table';
|
var tableId = this.name + '-table';
|
||||||
var $this = this; // Store this for child functions
|
var self = this; // Store this for child functions
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// Cells renderers
|
// Cells renderers
|
||||||
@ -101,7 +101,7 @@ GuiElement.prototype = {
|
|||||||
// Icon renderer, based on type (created on init methods in styles)
|
// Icon renderer, based on type (created on init methods in styles)
|
||||||
var renderTypeIcon = function(data, type, value){
|
var renderTypeIcon = function(data, type, value){
|
||||||
if( type == 'display' ) {
|
if( type == 'display' ) {
|
||||||
var css = $this.types[value.type].css;
|
var css = self.types[value.type].css;
|
||||||
return '<span class="' + css + '"></span> ' + renderEmptyCell(data);
|
return '<span class="' + css + '"></span> ' + renderEmptyCell(data);
|
||||||
} else {
|
} else {
|
||||||
return renderEmptyCell(data);
|
return renderEmptyCell(data);
|
||||||
@ -185,7 +185,7 @@ GuiElement.prototype = {
|
|||||||
columns: columns,
|
columns: columns,
|
||||||
})).appendTo('head');
|
})).appendTo('head');
|
||||||
|
|
||||||
$this.rest.overview(function(data) { // Gets "overview" data for table (table contents, but resume form)
|
self.rest.overview(function(data) { // Gets "overview" data for table (table contents, but resume form)
|
||||||
var table = gui.table(title, tableId);
|
var table = gui.table(title, tableId);
|
||||||
if (options.container === undefined) {
|
if (options.container === undefined) {
|
||||||
gui.appendToWorkspace('<div class="row"><div class="col-lg-12">' + table.text + '</div></div>');
|
gui.appendToWorkspace('<div class="row"><div class="col-lg-12">' + table.text + '</div></div>');
|
||||||
@ -205,11 +205,11 @@ GuiElement.prototype = {
|
|||||||
if( data.length > 1000 )
|
if( data.length > 1000 )
|
||||||
api.tools.blockUI();
|
api.tools.blockUI();
|
||||||
|
|
||||||
$this.rest.overview(function(data) { // Restore overview
|
self.rest.overview(function(data) { // Restore overview
|
||||||
setTimeout( function() {
|
setTimeout( function() {
|
||||||
tbl.fnClearTable();
|
tbl.fnClearTable();
|
||||||
tbl.fnAddData(data);
|
tbl.fnAddData(data);
|
||||||
onRefresh($this);
|
onRefresh(self);
|
||||||
api.tools.unblockUI();
|
api.tools.unblockUI();
|
||||||
}, 0);
|
}, 0);
|
||||||
}); // End restore overview
|
}); // End restore overview
|
||||||
@ -257,7 +257,7 @@ GuiElement.prototype = {
|
|||||||
var btn;
|
var btn;
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'new':
|
case 'new':
|
||||||
if(Object.keys($this.types).length === 0) {
|
if(Object.keys(self.types).length === 0) {
|
||||||
btn = {
|
btn = {
|
||||||
"sExtends" : "text",
|
"sExtends" : "text",
|
||||||
"sButtonText" : gui.config.dataTableButtons['new'].text,
|
"sButtonText" : gui.config.dataTableButtons['new'].text,
|
||||||
@ -269,7 +269,7 @@ GuiElement.prototype = {
|
|||||||
var newButtons = [];
|
var newButtons = [];
|
||||||
// Order buttons by name, much more easy for users... :-)
|
// Order buttons by name, much more easy for users... :-)
|
||||||
var order = [];
|
var order = [];
|
||||||
$.each($this.types, function(k, v){
|
$.each(self.types, function(k, v){
|
||||||
order.push({
|
order.push({
|
||||||
type: k,
|
type: k,
|
||||||
css: v.css,
|
css: v.css,
|
||||||
@ -424,7 +424,7 @@ GuiElement.prototype = {
|
|||||||
}
|
}
|
||||||
// if table rendered event
|
// if table rendered event
|
||||||
if( options.onLoad ) {
|
if( options.onLoad ) {
|
||||||
options.onLoad($this);
|
options.onLoad(self);
|
||||||
}
|
}
|
||||||
}); // End Overview data
|
}); // End Overview data
|
||||||
}); // End Tableinfo data
|
}); // End Tableinfo data
|
||||||
|
@ -3,11 +3,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Returns a form that will manage a gui description (new or edit)
|
// Returns a form that will manage a gui description (new or edit)
|
||||||
gui.fields = function(itemGui, item) {
|
gui.fieldsToHtml = function(itemGui, item, editing) {
|
||||||
var editing = item !== undefined; // Locate real Editing
|
var html = '';
|
||||||
item = item || {id:''};
|
|
||||||
var form = '<form class="form-horizontal" role="form">' +
|
|
||||||
'<input type="hidden" name="id" class="modal_field_data" value="' + item.id + '">';
|
|
||||||
// itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted)
|
// itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted)
|
||||||
$.each(itemGui, function(index, f){
|
$.each(itemGui, function(index, f){
|
||||||
gui.doLog(f);
|
gui.doLog(f);
|
||||||
@ -16,7 +13,7 @@
|
|||||||
if( f.gui.type == 'text' && f.gui.multiline ) {
|
if( f.gui.type == 'text' && f.gui.multiline ) {
|
||||||
f.gui.type = 'textbox';
|
f.gui.type = 'textbox';
|
||||||
}
|
}
|
||||||
form += api.templates.evaluate('tmpl_fld_'+f.gui.type, {
|
html += api.templates.evaluate('tmpl_fld_'+f.gui.type, {
|
||||||
value: item[f.name] || f.gui.value || f.gui.defvalue, // If no value present, use default value
|
value: item[f.name] || f.gui.value || f.gui.defvalue, // If no value present, use default value
|
||||||
values: f.gui.values,
|
values: f.gui.values,
|
||||||
label: f.gui.label,
|
label: f.gui.label,
|
||||||
@ -30,12 +27,27 @@
|
|||||||
css: 'modal_field_data',
|
css: 'modal_field_data',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
gui.form = {};
|
||||||
|
|
||||||
|
gui.form.fromFields = function(fields, item) {
|
||||||
|
var editing = item !== undefined; // Locate real Editing
|
||||||
|
item = item || {id:''};
|
||||||
|
var form = '<form class="form-horizontal" role="form">' +
|
||||||
|
'<input type="hidden" name="id" class="modal_field_data" value="' + item.id + '">';
|
||||||
|
if( fields.tabs ) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
form += gui.fieldsToHtml(fields, item, editing);
|
||||||
|
}
|
||||||
form += '</form>';
|
form += '</form>';
|
||||||
return form;
|
return form;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reads fields from a form
|
// Reads fields from a form
|
||||||
gui.fields.read = function(formSelector) {
|
gui.form.read = function(formSelector) {
|
||||||
var res = {};
|
var res = {};
|
||||||
$(formSelector + ' .modal_field_data').each(function(i, field) {
|
$(formSelector + ' .modal_field_data').each(function(i, field) {
|
||||||
var $field = $(field);
|
var $field = $(field);
|
||||||
@ -51,5 +63,6 @@
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}(window.gui = window.gui || {}, jQuery));
|
}(window.gui = window.gui || {}, jQuery));
|
||||||
|
|
@ -112,15 +112,15 @@
|
|||||||
gui.appendToWorkspace(gui.modal(id, title, content, actionButton, closeButton));
|
gui.appendToWorkspace(gui.modal(id, title, content, actionButton, closeButton));
|
||||||
id = '#' + id; // for jQuery
|
id = '#' + id; // for jQuery
|
||||||
|
|
||||||
$(id).modal({keyboard: false})
|
$(id).modal()
|
||||||
.on('hidden.bs.modal', function () {
|
.on('hidden.bs.modal', function () {
|
||||||
$(id).remove();
|
$(id).remove();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
gui.launchModalForm = function(title, content, onSuccess) {
|
gui.launchModalForm = function(title, form, onSuccess) {
|
||||||
var id = Math.random().toString().split('.')[1]; // Get a random ID for this modal
|
var id = Math.random().toString().split('.')[1]; // Get a random ID for this modal
|
||||||
gui.appendToWorkspace(gui.modal(id, title, content));
|
gui.appendToWorkspace(gui.modal(id, title, form));
|
||||||
id = '#' + id; // for jQuery
|
id = '#' + id; // for jQuery
|
||||||
|
|
||||||
// Get form
|
// Get form
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
<script src="{% get_static_prefix %}adm/js/api-spreadsheet.js"></script>
|
<script src="{% get_static_prefix %}adm/js/api-spreadsheet.js"></script>
|
||||||
|
|
||||||
<script src="{% get_static_prefix %}adm/js/gui.js"></script>
|
<script src="{% get_static_prefix %}adm/js/gui.js"></script>
|
||||||
<script src="{% get_static_prefix %}adm/js/gui-fields.js"></script>
|
<script src="{% get_static_prefix %}adm/js/gui-form.js"></script>
|
||||||
<script src="{% get_static_prefix %}adm/js/gui-element.js"></script>
|
<script src="{% get_static_prefix %}adm/js/gui-element.js"></script>
|
||||||
|
|
||||||
<!-- user interface management -->
|
<!-- user interface management -->
|
||||||
|
Loading…
Reference in New Issue
Block a user