import json import logging import usertypes from predicate_formula import NamedEntity, parse_predicate_formula_json, TreeConverter import predicate_formula log = logging.getLogger(__name__) class _DCEntityCollector(TreeConverter): def __init__(self): self.entities = [] def visit_Attribute(self, node): parent = self.visit(node.value) if parent == ["Name", "choice"]: self.entities.append(NamedEntity("choiceAttr", node.last_token.startpos, node.attr, None)) elif parent == ["Name", "rec"]: self.entities.append(NamedEntity("recCol", node.last_token.startpos, node.attr, None)) return ["Attr", parent, node.attr] def perform_dropdown_condition_renames(useractions, renames): """ Given a dict of column renames of the form {(table_id, col_id): new_col_id}, applies updates to the affected dropdown condition formulas. """ updates = [] for col in useractions.get_docmodel().columns.all: # Find all columns in the document that have dropdown conditions. try: widget_options = json.loads(col.widgetOptions) dc_formula = widget_options["dropdownCondition"]["text"] except (ValueError, KeyError): continue # Find out what table this column refers to and belongs to. ref_table_id = usertypes.get_referenced_table_id(col.type) self_table_id = col.parentId.tableId def renamer(subject): # subject.type is either choiceAttr or recCol, see _DCEntityCollector. table_id = ref_table_id if subject.type == "choiceAttr" else self_table_id # Dropdown conditions stay in widgetOptions, even when the current column type can't make # use of them. Thus, attributes of "choice" do not make sense for columns other than Ref and # RefList, but they may exist. # We set ref_table_id to None in this case, so table_id will be None for stray choiceAttrs, # therefore the subject will not be renamed. # Columns of "rec" are still renamed accordingly. return renames.get((table_id, subject.name)) new_dc_formula = predicate_formula.process_renames(dc_formula, _DCEntityCollector(), renamer) # The data engine stops processing remaining formulas when it hits an internal exception during # this renaming procedure. Parsing could potentially raise SyntaxErrors, so we must be careful # not to parse a possibly syntactically wrong formula, or handle SyntaxErrors explicitly. # Note that new_dc_formula was obtained from process_renames, where syntactically wrong formulas # are left untouched. It is anticipated that rename-induced changes will not introduce new # SyntaxErrors, so if the formula text is updated, the new version must be valid, hence safe # to parse without error handling. # This also serves as an optimization to avoid unnecessary parsing operations. if new_dc_formula != dc_formula: widget_options["dropdownCondition"]["text"] = new_dc_formula widget_options["dropdownCondition"]["parsed"] = parse_predicate_formula_json(new_dc_formula) updates.append((col, {"widgetOptions": json.dumps(widget_options)})) # Update the dropdown condition in the database. useractions.doBulkUpdateFromPairs('_grist_Tables_column', updates) def parse_dropdown_conditions(col_values): """ Parses any unparsed dropdown conditions in `col_values`. """ if 'widgetOptions' not in col_values: return col_values['widgetOptions'] = [parse_dropdown_condition(widget_options_json) for widget_options_json in col_values['widgetOptions']] def parse_dropdown_condition(widget_options_json): """ Parses `dropdownCondition.text` in `widget_options_json` and stores the parsed representation in `dropdownCondition.parsed`. If `dropdownCondition.parsed` is already set, parsing is skipped (as an optimization). Clients are responsible for including just `dropdownCondition.text` when creating new (or updating existing) dropdown conditions. Returns an updated copy of `widget_options_json` or the original widget_options_json if parsing was skipped. """ try: widget_options = json.loads(widget_options_json) if 'dropdownCondition' not in widget_options: return widget_options_json dropdown_condition = widget_options['dropdownCondition'] if 'parsed' in dropdown_condition: return widget_options_json dropdown_condition['parsed'] = parse_predicate_formula_json(dropdown_condition['text']) return json.dumps(widget_options) except (TypeError, ValueError): return widget_options_json