mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Improve handling of nulls and default values in the data editor. Fixes #2257
This commit is contained in:
parent
05787fdba9
commit
4f9628ed43
@ -1,4 +1,5 @@
|
|||||||
SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype
|
SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype,
|
||||||
|
att.attnotnull as not_null, att.atthasdef as has_default_val
|
||||||
FROM pg_attribute att
|
FROM pg_attribute att
|
||||||
JOIN pg_type ty ON ty.oid=atttypid
|
JOIN pg_type ty ON ty.oid=atttypid
|
||||||
JOIN pg_namespace tn ON tn.oid=ty.typnamespace
|
JOIN pg_namespace tn ON tn.oid=ty.typnamespace
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype
|
SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype,
|
||||||
|
att.attnotnull as not_null, att.atthasdef as has_default_val
|
||||||
FROM pg_attribute att
|
FROM pg_attribute att
|
||||||
JOIN pg_type ty ON ty.oid=atttypid
|
JOIN pg_type ty ON ty.oid=atttypid
|
||||||
JOIN pg_namespace tn ON tn.oid=ty.typnamespace
|
JOIN pg_namespace tn ON tn.oid=ty.typnamespace
|
||||||
|
@ -780,4 +780,4 @@ lgg-el-container[el=md] .pg-el-lg-8,
|
|||||||
}
|
}
|
||||||
.user-language select{
|
.user-language select{
|
||||||
height: 25px !important;
|
height: 25px !important;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,12 @@
|
|||||||
|
|
||||||
// When text editor opens
|
// When text editor opens
|
||||||
this.loadValue = function (item) {
|
this.loadValue = function (item) {
|
||||||
if (item[args.column.pos] === "") {
|
var col = args.column;
|
||||||
|
|
||||||
|
if (_.isUndefined(item[args.column.pos]) && col.has_default_val) {
|
||||||
|
$input.val("");
|
||||||
|
}
|
||||||
|
else if (item[args.column.pos] === "") {
|
||||||
$input.val("''");
|
$input.val("''");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -145,7 +150,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.isValueChanged = function () {
|
this.isValueChanged = function () {
|
||||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
// Use _.isNull(value) for comparison for null instead of
|
||||||
|
// defaultValue == null, because it returns true for undefined value.
|
||||||
|
if ($input.val() == "" && _.isUndefined(defaultValue)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return (!($input.val() == "" && _.isNull(defaultValue))) &&
|
||||||
|
($input.val() != defaultValue);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.validate = function () {
|
this.validate = function () {
|
||||||
@ -253,7 +265,7 @@
|
|||||||
|
|
||||||
this.loadValue = function (item) {
|
this.loadValue = function (item) {
|
||||||
var data = defaultValue = item[args.column.pos];
|
var data = defaultValue = item[args.column.pos];
|
||||||
if (typeof data === "object" && !Array.isArray(data)) {
|
if (data && typeof data === "object" && !Array.isArray(data)) {
|
||||||
data = JSON.stringify(data);
|
data = JSON.stringify(data);
|
||||||
} else if (Array.isArray(data)) {
|
} else if (Array.isArray(data)) {
|
||||||
var temp = [];
|
var temp = [];
|
||||||
@ -282,7 +294,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.isValueChanged = function () {
|
this.isValueChanged = function () {
|
||||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
if ($input.val() == "" && _.isUndefined(defaultValue)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return (!($input.val() == "" && _.isNull(defaultValue))) && ($input.val() != defaultValue);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.validate = function () {
|
this.validate = function () {
|
||||||
@ -498,6 +514,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.validate = function () {
|
this.validate = function () {
|
||||||
|
if (args.column.validator) {
|
||||||
|
var validationResults = args.column.validator(this.serializeValue());
|
||||||
|
if (!validationResults.valid) {
|
||||||
|
return validationResults;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
msg: null
|
msg: null
|
||||||
@ -837,7 +859,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.isValueChanged = function () {
|
this.isValueChanged = function () {
|
||||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
if ($input.val() == "" && _.isUndefined(defaultValue)) {
|
||||||
|
return false;
|
||||||
|
} else if ($input.val() == "" && defaultValue == "") {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return (!($input.val() == "" && _.isNull(defaultValue ))) &&
|
||||||
|
($input.val() != defaultValue);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.validate = function () {
|
this.validate = function () {
|
||||||
|
@ -19,8 +19,15 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function JsonFormatter(row, cell, value, columnDef, dataContext) {
|
function JsonFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
if (value == null || value === "") {
|
// If column has default value, set placeholder
|
||||||
return "";
|
if (_.isUndefined(value) && columnDef.has_default_val) {
|
||||||
|
return "<span class='pull-left disabled_cell'>[default]</span>";
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
(_.isUndefined(value) && columnDef.not_null) ||
|
||||||
|
(_.isUndefined(value) || value === null)
|
||||||
|
) {
|
||||||
|
return "<span class='pull-left disabled_cell'>[null]</span>";
|
||||||
} else {
|
} else {
|
||||||
// Stringify only if it's json object
|
// Stringify only if it's json object
|
||||||
if (typeof value === "object" && !Array.isArray(value)) {
|
if (typeof value === "object" && !Array.isArray(value)) {
|
||||||
@ -42,11 +49,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function NumbersFormatter(row, cell, value, columnDef, dataContext) {
|
function NumbersFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
if (_.isUndefined(value) || value === null) {
|
// If column has default value, set placeholder
|
||||||
return "<span class='pull-right'>[null]</span>";
|
if (_.isUndefined(value) && columnDef.has_default_val) {
|
||||||
|
return "<span class='pull-right disabled_cell'>[default]</span>";
|
||||||
}
|
}
|
||||||
else if (value === "") {
|
else if (
|
||||||
return '';
|
(_.isUndefined(value) || value === null || value === "") ||
|
||||||
|
(_.isUndefined(value) && columnDef.not_null)
|
||||||
|
) {
|
||||||
|
return "<span class='pull-right disabled_cell'>[null]</span>";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "<span style='float:right'>" + _.escape(value) + "</span>";
|
return "<span style='float:right'>" + _.escape(value) + "</span>";
|
||||||
@ -57,17 +68,30 @@
|
|||||||
/* Checkbox has 3 states
|
/* Checkbox has 3 states
|
||||||
* 1) checked=true
|
* 1) checked=true
|
||||||
* 2) unchecked=false
|
* 2) unchecked=false
|
||||||
* 3) indeterminate=null/''
|
* 3) indeterminate=null
|
||||||
*/
|
*/
|
||||||
if (value == null || value === "") {
|
if (_.isUndefined(value) && columnDef.has_default_val) {
|
||||||
return "<span class='pull-left'>[null]</span>";
|
return "<span class='pull-left disabled_cell'>[default]</span>";
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
(_.isUndefined(value) && columnDef.not_null) ||
|
||||||
|
(value == null || value === "")
|
||||||
|
) {
|
||||||
|
return "<span class='pull-left disabled_cell'>[null]</span>";
|
||||||
}
|
}
|
||||||
return value ? "true" : "false";
|
return value ? "true" : "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextFormatter(row, cell, value, columnDef, dataContext) {
|
function TextFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
if (_.isUndefined(value) || value === null) {
|
// If column has default value, set placeholder
|
||||||
return "<span class='pull-left'>[null]</span>";
|
if (_.isUndefined(value) && columnDef.has_default_val) {
|
||||||
|
return "<span class='pull-left disabled_cell'>[default]</span>";
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
(_.isUndefined(value) && columnDef.not_null) ||
|
||||||
|
(_.isUndefined(value) || _.isNull(value))
|
||||||
|
) {
|
||||||
|
return "<span class='pull-left disabled_cell'>[null]</span>";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return _.escape(value);
|
return _.escape(value);
|
||||||
|
@ -441,8 +441,23 @@ def get_columns(trans_id):
|
|||||||
columns = dict()
|
columns = dict()
|
||||||
columns_info = None
|
columns_info = None
|
||||||
primary_keys = None
|
primary_keys = None
|
||||||
|
rset = None
|
||||||
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
|
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
|
||||||
if status and conn is not None and session_obj is not None:
|
if status and conn is not None and session_obj is not None:
|
||||||
|
|
||||||
|
ver = conn.manager.version
|
||||||
|
# Get the template path for the column
|
||||||
|
template_path = 'column/sql/#{0}#'.format(ver)
|
||||||
|
command_obj = pickle.loads(session_obj['command_obj'])
|
||||||
|
if hasattr(command_obj, 'obj_id'):
|
||||||
|
SQL = render_template("/".join([template_path,
|
||||||
|
'nodes.sql']),
|
||||||
|
tid=command_obj.obj_id)
|
||||||
|
# rows with attribute not_null
|
||||||
|
status, rset = conn.execute_2darray(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rset)
|
||||||
|
|
||||||
# Check PK column info is available or not
|
# Check PK column info is available or not
|
||||||
if 'primary_keys' in session_obj:
|
if 'primary_keys' in session_obj:
|
||||||
primary_keys = session_obj['primary_keys']
|
primary_keys = session_obj['primary_keys']
|
||||||
@ -450,10 +465,17 @@ def get_columns(trans_id):
|
|||||||
# Fetch column information
|
# Fetch column information
|
||||||
columns_info = conn.get_column_info()
|
columns_info = conn.get_column_info()
|
||||||
if columns_info is not None:
|
if columns_info is not None:
|
||||||
for col in columns_info:
|
for key, col in enumerate(columns_info):
|
||||||
col_type = dict()
|
col_type = dict()
|
||||||
col_type['type_code'] = col['type_code']
|
col_type['type_code'] = col['type_code']
|
||||||
col_type['type_name'] = None
|
col_type['type_name'] = None
|
||||||
|
if rset:
|
||||||
|
col_type['not_null'] = col['not_null'] = \
|
||||||
|
rset['rows'][key]['not_null']
|
||||||
|
|
||||||
|
col_type['has_default_val'] = col['has_default_val'] = \
|
||||||
|
rset['rows'][key]['has_default_val']
|
||||||
|
|
||||||
columns[col['name']] = col_type
|
columns[col['name']] = col_type
|
||||||
|
|
||||||
# As we changed the transaction object we need to
|
# As we changed the transaction object we need to
|
||||||
@ -603,6 +625,7 @@ def save(trans_id):
|
|||||||
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
|
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
|
||||||
if status and conn is not None \
|
if status and conn is not None \
|
||||||
and trans_obj is not None and session_obj is not None:
|
and trans_obj is not None and session_obj is not None:
|
||||||
|
setattr(trans_obj, 'columns_info', session_obj['columns_info'])
|
||||||
|
|
||||||
# If there is no primary key found then return from the function.
|
# If there is no primary key found then return from the function.
|
||||||
if len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0:
|
if len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0:
|
||||||
|
@ -442,6 +442,25 @@ class TableCommand(GridCommand):
|
|||||||
|
|
||||||
# For newly added rows
|
# For newly added rows
|
||||||
if of_type == 'added':
|
if of_type == 'added':
|
||||||
|
|
||||||
|
# When new rows are added, only changed columns data is
|
||||||
|
# sent from client side. But if column is not_null and has
|
||||||
|
# no_default_value, set column to blank, instead
|
||||||
|
# of not null which is set by default.
|
||||||
|
column_data = {}
|
||||||
|
column_type = {}
|
||||||
|
pk_names, primary_keys = self.get_primary_keys()
|
||||||
|
|
||||||
|
for each_col in self.columns_info:
|
||||||
|
if (
|
||||||
|
self.columns_info[each_col]['not_null'] and
|
||||||
|
not self.columns_info[each_col][
|
||||||
|
'has_default_val']
|
||||||
|
):
|
||||||
|
column_data[each_col] = None
|
||||||
|
column_type[each_col] =\
|
||||||
|
self.columns_info[each_col]['type_name']
|
||||||
|
|
||||||
for each_row in changed_data[of_type]:
|
for each_row in changed_data[of_type]:
|
||||||
data = changed_data[of_type][each_row]['data']
|
data = changed_data[of_type][each_row]['data']
|
||||||
# Remove our unique tracking key
|
# Remove our unique tracking key
|
||||||
@ -450,12 +469,19 @@ class TableCommand(GridCommand):
|
|||||||
data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
|
data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
|
||||||
list_of_rowid.append(data.get('__temp_PK'))
|
list_of_rowid.append(data.get('__temp_PK'))
|
||||||
|
|
||||||
|
# Update columns value and data type
|
||||||
|
# with columns having not_null=False and has
|
||||||
|
# no default value
|
||||||
|
column_data.update(data)
|
||||||
|
column_type.update(data_type)
|
||||||
|
|
||||||
sql = render_template("/".join([self.sql_path, 'insert.sql']),
|
sql = render_template("/".join([self.sql_path, 'insert.sql']),
|
||||||
data_to_be_saved=data,
|
data_to_be_saved=column_data,
|
||||||
primary_keys=None,
|
primary_keys=None,
|
||||||
object_name=self.object_name,
|
object_name=self.object_name,
|
||||||
nsp_name=self.nsp_name,
|
nsp_name=self.nsp_name,
|
||||||
data_type=data_type)
|
data_type=column_type,
|
||||||
|
pk_names=pk_names)
|
||||||
list_of_sql.append(sql)
|
list_of_sql.append(sql)
|
||||||
|
|
||||||
# For updated rows
|
# For updated rows
|
||||||
|
@ -420,6 +420,16 @@ input.editor-checkbox:focus {
|
|||||||
background: #e46b6b;
|
background: #e46b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disabled row */
|
||||||
|
.grid-canvas .disabled_row {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled cell */
|
||||||
|
.grid-canvas .disabled_cell {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
/* color the first column */
|
/* color the first column */
|
||||||
.sr .sc:first-child {
|
.sr .sc:first-child {
|
||||||
background-color: #2c76b4;
|
background-color: #2c76b4;
|
||||||
@ -445,4 +455,4 @@ input.editor-checkbox:focus {
|
|||||||
|
|
||||||
.sr.ui-widget-content {
|
.sr.ui-widget-content {
|
||||||
border-top: 1px solid silver;
|
border-top: 1px solid silver;
|
||||||
}
|
}
|
@ -557,7 +557,9 @@ define(
|
|||||||
id: c.name,
|
id: c.name,
|
||||||
pos: c.pos,
|
pos: c.pos,
|
||||||
field: c.name,
|
field: c.name,
|
||||||
name: c.label
|
name: c.label,
|
||||||
|
not_null: c.not_null,
|
||||||
|
has_default_val: c.has_default_val
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the columns width based on data type
|
// Get the columns width based on data type
|
||||||
@ -625,6 +627,12 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Disable rows having default values
|
||||||
|
if (!_.isUndefined(self.handler.rows_to_disable) &&
|
||||||
|
_.indexOf(self.handler.rows_to_disable, i) !== -1
|
||||||
|
) {
|
||||||
|
cssClass += ' disabled_row';
|
||||||
|
}
|
||||||
return {'cssClasses': cssClass};
|
return {'cssClasses': cssClass};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -702,6 +710,14 @@ define(
|
|||||||
// This will be used to collect primary key for that row
|
// This will be used to collect primary key for that row
|
||||||
grid.onBeforeEditCell.subscribe(function (e, args) {
|
grid.onBeforeEditCell.subscribe(function (e, args) {
|
||||||
var before_data = args.item;
|
var before_data = args.item;
|
||||||
|
|
||||||
|
// If newly added row is saved but grid is not refreshed,
|
||||||
|
// then disable cell editing for that row
|
||||||
|
if(self.handler.rows_to_disable &&
|
||||||
|
_.contains(self.handler.rows_to_disable, args.row)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(self.handler.can_edit && before_data && '__temp_PK' in before_data) {
|
if(self.handler.can_edit && before_data && '__temp_PK' in before_data) {
|
||||||
var _pk = before_data.__temp_PK,
|
var _pk = before_data.__temp_PK,
|
||||||
_keys = self.handler.primary_keys,
|
_keys = self.handler.primary_keys,
|
||||||
@ -1629,6 +1645,8 @@ define(
|
|||||||
self.query_start_time = new Date();
|
self.query_start_time = new Date();
|
||||||
self.rows_affected = 0;
|
self.rows_affected = 0;
|
||||||
self._init_polling_flags();
|
self._init_polling_flags();
|
||||||
|
// keep track of newly added rows
|
||||||
|
self.rows_to_disable = new Array();
|
||||||
|
|
||||||
self.trigger(
|
self.trigger(
|
||||||
'pgadmin-sqleditor:loading-icon:show',
|
'pgadmin-sqleditor:loading-icon:show',
|
||||||
@ -2077,7 +2095,9 @@ define(
|
|||||||
'label': column_label,
|
'label': column_label,
|
||||||
'cell': col_cell,
|
'cell': col_cell,
|
||||||
'can_edit': self.can_edit,
|
'can_edit': self.can_edit,
|
||||||
'type': type
|
'type': type,
|
||||||
|
'not_null': c.not_null,
|
||||||
|
'has_default_val': c.has_default_val
|
||||||
};
|
};
|
||||||
columns.push(col);
|
columns.push(col);
|
||||||
});
|
});
|
||||||
@ -2320,6 +2340,10 @@ define(
|
|||||||
grid.setSelectedRows([]);
|
grid.setSelectedRows([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add last row(new row) to keep track of it
|
||||||
|
if (is_added) {
|
||||||
|
self.rows_to_disable.push(grid.getDataLength()-1);
|
||||||
|
}
|
||||||
// Reset data store
|
// Reset data store
|
||||||
self.data_store = {
|
self.data_store = {
|
||||||
'added': {},
|
'added': {},
|
||||||
|
Loading…
Reference in New Issue
Block a user