mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add RenameChoices user action
Summary: ["RenameChoices", table_id, col_id, renames] Updates the data in a Choice/ChoiceList column to reflect the new choice names. `renames` should be a dict of `{old_choice_name: new_choice_name}`. This doesn't touch the choices configuration in widgetOptions, that must be done separately. Frontend to be done in another diff. Test Plan: Added two Python unit tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3050
This commit is contained in:
parent
02fd71d9bb
commit
d4ea5b3761
@ -225,6 +225,20 @@ class BaseColumn(object):
|
|||||||
# pylint: disable=no-self-use, unused-argument
|
# pylint: disable=no-self-use, unused-argument
|
||||||
return values, []
|
return values, []
|
||||||
|
|
||||||
|
def rename_choices(self, renames):
|
||||||
|
row_ids = []
|
||||||
|
values = []
|
||||||
|
for row_id, value in enumerate(self._data):
|
||||||
|
if value is not None and self.type_obj.is_right_type(value):
|
||||||
|
value = self._rename_cell_choice(renames, value)
|
||||||
|
if value is not None:
|
||||||
|
row_ids.append(row_id)
|
||||||
|
values.append(value)
|
||||||
|
return row_ids, values
|
||||||
|
|
||||||
|
def _rename_cell_choice(self, renames, value):
|
||||||
|
return renames.get(value, value)
|
||||||
|
|
||||||
|
|
||||||
class DataColumn(BaseColumn):
|
class DataColumn(BaseColumn):
|
||||||
"""
|
"""
|
||||||
@ -361,6 +375,9 @@ class ChoiceListColumn(BaseColumn):
|
|||||||
def _make_rich_value(self, typed_value):
|
def _make_rich_value(self, typed_value):
|
||||||
return () if typed_value is None else typed_value
|
return () if typed_value is None else typed_value
|
||||||
|
|
||||||
|
def _rename_cell_choice(self, renames, value):
|
||||||
|
return tuple(renames.get(choice, choice) for choice in value)
|
||||||
|
|
||||||
|
|
||||||
class BaseReferenceColumn(BaseColumn):
|
class BaseReferenceColumn(BaseColumn):
|
||||||
"""
|
"""
|
||||||
|
@ -3,6 +3,7 @@ import functools
|
|||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@ -203,7 +204,10 @@ class EngineTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
Prints out_actions in human-readable format, for help in writing / debugging tets.
|
Prints out_actions in human-readable format, for help in writing / debugging tets.
|
||||||
"""
|
"""
|
||||||
print("\n".join(self._formatActionGroup(out_actions.__dict__)))
|
pprint({
|
||||||
|
k: [get_comparable_repr(action) for action in getattr(out_actions, k)]
|
||||||
|
for k in self.action_group_action_fields
|
||||||
|
})
|
||||||
|
|
||||||
def assertTableData(self, table_name, data=[], cols="all", rows="all", sort=None):
|
def assertTableData(self, table_name, data=[], cols="all", rows="all", sort=None):
|
||||||
"""
|
"""
|
||||||
|
@ -352,3 +352,78 @@ class TestSummaryChoiceList(EngineTestCase):
|
|||||||
starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList")
|
starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList")
|
||||||
self.assertTables([starting_table, summary_table])
|
self.assertTables([starting_table, summary_table])
|
||||||
self.assertTableData('GristSummary_6_Source', data=data)
|
self.assertTableData('GristSummary_6_Source', data=data)
|
||||||
|
|
||||||
|
def test_rename_choices(self):
|
||||||
|
self.load_sample(self.sample)
|
||||||
|
|
||||||
|
# Create a summary section, grouped by both choicelist columns.
|
||||||
|
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12]])
|
||||||
|
|
||||||
|
summary_table = Table(
|
||||||
|
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
|
||||||
|
columns=[
|
||||||
|
Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||||
|
Column(14, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12),
|
||||||
|
Column(15, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
|
||||||
|
formula="table.getSummarySourceGroup(rec)"),
|
||||||
|
Column(16, "count", "Int", isFormula=True, summarySourceCol=0,
|
||||||
|
formula="len($group)"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTables([self.starting_table, summary_table])
|
||||||
|
|
||||||
|
# Rename all the choices
|
||||||
|
out_actions = self.apply_user_action(
|
||||||
|
["RenameChoices", "Source", "choices1", {"a": "aa", "b": "bb"}])
|
||||||
|
self.apply_user_action(
|
||||||
|
["RenameChoices", "Source", "choices2", {"c": "cc", "d": "dd"}])
|
||||||
|
|
||||||
|
# Actions from renaming choices1 only
|
||||||
|
self.assertPartialOutActions(out_actions, {'stored': [
|
||||||
|
['UpdateRecord', 'Source', 21, {'choices1': ['L', u'aa', u'bb']}],
|
||||||
|
['BulkAddRecord',
|
||||||
|
'GristSummary_6_Source',
|
||||||
|
[5, 6, 7, 8],
|
||||||
|
{'choices1': [u'aa', u'aa', u'bb', u'bb'],
|
||||||
|
'choices2': [u'c', u'd', u'c', u'd']}],
|
||||||
|
['BulkUpdateRecord',
|
||||||
|
'GristSummary_6_Source',
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
{'count': [0, 0, 0, 0, 1, 1, 1, 1]}],
|
||||||
|
['BulkUpdateRecord',
|
||||||
|
'GristSummary_6_Source',
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
{'group': [['L'],
|
||||||
|
['L'],
|
||||||
|
['L'],
|
||||||
|
['L'],
|
||||||
|
['L', 21],
|
||||||
|
['L', 21],
|
||||||
|
['L', 21],
|
||||||
|
['L', 21]]}]
|
||||||
|
]})
|
||||||
|
|
||||||
|
# Final Source table is essentially the same as before, just with each letter doubled
|
||||||
|
self.assertTableData('Source', data=[
|
||||||
|
["id", "choices1", "choices2", "other"],
|
||||||
|
[21, ["aa", "bb"], ["cc", "dd"], "foo"],
|
||||||
|
])
|
||||||
|
|
||||||
|
# Final summary table is very similar to before, but with two empty chunks of 4 rows
|
||||||
|
# left over from each rename
|
||||||
|
self.assertTableData('GristSummary_6_Source', data=[
|
||||||
|
["id", "choices1", "choices2", "group", "count"],
|
||||||
|
[1, "a", "c", [], 0],
|
||||||
|
[2, "a", "d", [], 0],
|
||||||
|
[3, "b", "c", [], 0],
|
||||||
|
[4, "b", "d", [], 0],
|
||||||
|
[5, "aa", "c", [], 0],
|
||||||
|
[6, "aa", "d", [], 0],
|
||||||
|
[7, "bb", "c", [], 0],
|
||||||
|
[8, "bb", "d", [], 0],
|
||||||
|
[9, "aa", "cc", [21], 1],
|
||||||
|
[10, "aa", "dd", [21], 1],
|
||||||
|
[11, "bb", "cc", [21], 1],
|
||||||
|
[12, "bb", "dd", [21], 1],
|
||||||
|
])
|
||||||
|
@ -770,3 +770,70 @@ class TestUserActions(test_engine.EngineTestCase):
|
|||||||
['id', 'indentation'],
|
['id', 'indentation'],
|
||||||
[ 3, 0],
|
[ 3, 0],
|
||||||
])
|
])
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_rename_choices(self):
|
||||||
|
sample = testutil.parse_test_sample({
|
||||||
|
"SCHEMA": [
|
||||||
|
[1, "ChoiceTable", [
|
||||||
|
[1, "ChoiceColumn", "Choice", False, "", "ChoiceColumn", ""],
|
||||||
|
]],
|
||||||
|
[2, "ChoiceListTable", [
|
||||||
|
[2, "ChoiceListColumn", "ChoiceList", False, "", "ChoiceListColumn", ""],
|
||||||
|
]],
|
||||||
|
],
|
||||||
|
"DATA": {
|
||||||
|
"ChoiceTable": [
|
||||||
|
["id", "ChoiceColumn"],
|
||||||
|
[1, "a"],
|
||||||
|
[2, "b"],
|
||||||
|
[3, "c"],
|
||||||
|
[4, "d"],
|
||||||
|
[5, None],
|
||||||
|
],
|
||||||
|
"ChoiceListTable": [
|
||||||
|
["id", "ChoiceListColumn"],
|
||||||
|
[1, ["a"]],
|
||||||
|
[2, ["b"]],
|
||||||
|
[3, ["c"]],
|
||||||
|
[4, ["d"]],
|
||||||
|
[5, None],
|
||||||
|
[7, ["a", "b"]],
|
||||||
|
[8, ["b", "c"]],
|
||||||
|
[9, ["a", "c"]],
|
||||||
|
[10, ["a", "b", "c"]],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.load_sample(sample)
|
||||||
|
|
||||||
|
# Renames go in a loop to make sure that works correctly
|
||||||
|
# a -> b -> c -> a -> b -> ...
|
||||||
|
renames = {"a": "b", "b": "c", "c": "a"}
|
||||||
|
self.apply_user_action(
|
||||||
|
["RenameChoices", "ChoiceTable", "ChoiceColumn", renames])
|
||||||
|
self.apply_user_action(
|
||||||
|
["RenameChoices", "ChoiceListTable", "ChoiceListColumn", renames])
|
||||||
|
|
||||||
|
self.assertTableData('ChoiceTable', data=[
|
||||||
|
["id", "ChoiceColumn"],
|
||||||
|
[1, "b"],
|
||||||
|
[2, "c"],
|
||||||
|
[3, "a"],
|
||||||
|
[4, "d"],
|
||||||
|
[5, None],
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertTableData('ChoiceListTable', data=[
|
||||||
|
["id", "ChoiceListColumn"],
|
||||||
|
[1, ["b"]],
|
||||||
|
[2, ["c"]],
|
||||||
|
[3, ["a"]],
|
||||||
|
[4, ["d"]],
|
||||||
|
[5, None],
|
||||||
|
[7, ["b", "c"]],
|
||||||
|
[8, ["c", "a"]],
|
||||||
|
[9, ["b", "a"]],
|
||||||
|
[10, ["b", "c", "a"]],
|
||||||
|
])
|
||||||
|
@ -12,7 +12,7 @@ from acl_formula import parse_acl_formula_json
|
|||||||
import actions
|
import actions
|
||||||
import column
|
import column
|
||||||
import identifiers
|
import identifiers
|
||||||
from objtypes import strict_equal
|
from objtypes import strict_equal, encode_object
|
||||||
import schema
|
import schema
|
||||||
from schema import RecalcWhen
|
from schema import RecalcWhen
|
||||||
import summary
|
import summary
|
||||||
@ -1247,6 +1247,25 @@ class UserActions(object):
|
|||||||
self.SetDisplayFormula(dst_col.parentId.tableId, None, dst_col.id,
|
self.SetDisplayFormula(dst_col.parentId.tableId, None, dst_col.id,
|
||||||
re.sub((r'\$%s\b' % src_col.colId), '$' + dst_col.colId, src_col.displayCol.formula))
|
re.sub((r'\$%s\b' % src_col.colId), '$' + dst_col.colId, src_col.displayCol.formula))
|
||||||
|
|
||||||
|
@useraction
|
||||||
|
def RenameChoices(self, table_id, col_id, renames):
|
||||||
|
"""
|
||||||
|
Updates the data in a Choice/ChoiceList column to reflect the new choice names.
|
||||||
|
`renames` should be a dict of {old_choice_name: new_choice_name}.
|
||||||
|
This doesn't touch the choices configuration in widgetOptions, that must be done separately.
|
||||||
|
"""
|
||||||
|
|
||||||
|
table = self._engine.tables[table_id]
|
||||||
|
col = table.get_column(col_id)
|
||||||
|
|
||||||
|
if col.is_formula():
|
||||||
|
# We don't set the values of formula columns, they should just recalculate themselves
|
||||||
|
return None
|
||||||
|
|
||||||
|
row_ids, values = col.rename_choices(renames)
|
||||||
|
values = [encode_object(v) for v in values]
|
||||||
|
return self.BulkUpdateRecord(table_id, row_ids, {col_id: values})
|
||||||
|
|
||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
# User actions on tables.
|
# User actions on tables.
|
||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user