mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +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