You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/sandbox/grist/dropdown_condition.py

111 lines
4.5 KiB

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 (json.JSONDecodeError, 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