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
|
||||
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):
|
||||
"""
|
||||
@ -361,6 +375,9 @@ class ChoiceListColumn(BaseColumn):
|
||||
def _make_rich_value(self, 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):
|
||||
"""
|
||||
|
@ -3,6 +3,7 @@ import functools
|
||||
import json
|
||||
import unittest
|
||||
from collections import namedtuple
|
||||
from pprint import pprint
|
||||
|
||||
import six
|
||||
|
||||
@ -203,7 +204,10 @@ class EngineTestCase(unittest.TestCase):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -352,3 +352,78 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList")
|
||||
self.assertTables([starting_table, summary_table])
|
||||
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'],
|
||||
[ 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 column
|
||||
import identifiers
|
||||
from objtypes import strict_equal
|
||||
from objtypes import strict_equal, encode_object
|
||||
import schema
|
||||
from schema import RecalcWhen
|
||||
import summary
|
||||
@ -1247,6 +1247,25 @@ class UserActions(object):
|
||||
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))
|
||||
|
||||
@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.
|
||||
#----------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user