(core) Barely working reference lists in frontend

Summary:
This makes it possible to set the type of a column to ReferenceList, but the UI is terrible

ReferenceList.ts is a mishmash of ChoiceList and Reference that sort of works but something about the CSS is clearly broken

ReferenceListEditor is just a text editor, you have to type in a JSON array of row IDs. Ignore the value that's present when you start editing. I can maybe try mashing together ReferenceEditor and ChoiceListEditor but it doesn't seem wise.
I think @georgegevoian should take over here. Reviewing the diff as it is to check for obvious issues is probably good but I don't think it's worth trying to land/merge anything.

Test Plan: none

Reviewers: dsagal

Reviewed By: dsagal

Subscribers: georgegevoian

Differential Revision: https://phab.getgrist.com/D2914
This commit is contained in:
Alex Hall
2021-07-23 17:29:35 +02:00
parent 8d68c1c567
commit 04e5d90f86
17 changed files with 228 additions and 41 deletions

View File

@@ -440,6 +440,14 @@ class ReferenceListColumn(BaseReferenceColumn):
ReferenceListColumn maintains for each row a list of references (row IDs) into another table.
Accessing them yields RecordSets.
"""
def set(self, row_id, value):
if isinstance(value, six.string_types) and value.startswith(u'['):
try:
value = json.loads(value)
except Exception:
pass
super(ReferenceListColumn, self).set(row_id, value)
def _update_references(self, row_id, old_list, new_list):
for old_value in old_list or ():
self._relation.remove_reference(row_id, old_value)

View File

@@ -188,10 +188,10 @@ def ISREF(value):
"""
Checks whether a value is a table record.
For example, if a column person is of type Reference to the People table, then ISREF($person)
is True.
Similarly, ISREF(People.lookupOne(name=$name)) is True. For any other type of value,
ISREF() would evaluate to False.
For example, if a column `person` is of type Reference to the `People` table,
then `ISREF($person)` is `True`.
Similarly, `ISREF(People.lookupOne(name=$name))` is `True`. For any other type of value,
`ISREF()` would evaluate to `False`.
>>> ISREF(17)
False
@@ -202,6 +202,25 @@ def ISREF(value):
return isinstance(value, Record)
def ISREFLIST(value):
"""
Checks whether a value is a [`RecordSet`](#recordset),
the type of values in Reference List columns.
For example, if a column `people` is of type Reference List to the `People` table,
then `ISREFLIST($people)` is `True`.
Similarly, `ISREFLIST(People.lookupRecords(name=$name))` is `True`. For any other type of value,
`ISREFLIST()` would evaluate to `False`.
>>> ISREFLIST(17)
False
>>> ISREFLIST("Roger")
False
"""
return isinstance(value, RecordSet)
def ISTEXT(value):
"""
Checks whether a value is text.

View File

@@ -286,7 +286,7 @@ class RecordList(list):
self._sort_by = sort_by
def __repr__(self):
return "RecordList(%r, group_by=%r, sort_by=%r)" % (
return "RecordList(%s, group_by=%r, sort_by=%r)" % (
list.__repr__(self), self._group_by, self._sort_by)

View File

@@ -121,11 +121,16 @@ class SummaryActions(object):
"""
key = tuple(sorted(int(c) for c in source_groupby_columns))
groupby_colinfo = [_make_col_info(col=c,
isFormula=False,
formula='',
type='Choice' if c.type == 'ChoiceList' else c.type)
for c in source_groupby_columns]
groupby_colinfo = [
_make_col_info(
col=c,
isFormula=False,
formula='',
type='Choice' if c.type == 'ChoiceList' else
c.type.replace('RefList:', 'Ref:')
)
for c in source_groupby_columns
]
summary_table = next((t for t in source_table.summaryTables if t.summaryKey == key), None)
created = False
if not summary_table:

View File

@@ -270,7 +270,7 @@ class Table(object):
self._summary_simple = not any(
isinstance(
self._summary_source_table.all_columns.get(group_col),
column.ChoiceListColumn
(column.ChoiceListColumn, column.ReferenceListColumn)
)
for group_col in groupby_cols
)
@@ -299,12 +299,13 @@ class Table(object):
@usertypes.formulaType(usertypes.ReferenceList(summary_table.table_id))
def _updateSummary(rec, table): # pylint: disable=unused-argument
# Create a row in the summary table for every combination of values in
# ChoiceList columns
# list type columns
lookup_values = []
for group_col in groupby_cols:
lookup_value = getattr(rec, group_col)
if isinstance(self.all_columns[group_col], column.ChoiceListColumn):
# Check that ChoiceList cells have appropriate types.
if isinstance(self.all_columns[group_col],
(column.ChoiceListColumn, column.ReferenceListColumn)):
# Check that ChoiceList/ReferenceList cells have appropriate types.
# Don't iterate over characters of a string.
if isinstance(lookup_value, (six.binary_type, six.text_type)):
return []

View File

@@ -453,6 +453,14 @@ class ReferenceList(BaseColumnType):
return "RefList"
def do_convert(self, value):
if isinstance(value, six.string_types):
# If it's a string that looks like JSON, try to parse it as such.
if value.startswith('['):
try:
value = json.loads(value)
except Exception:
pass
if isinstance(value, RecordSet):
assert value._table.table_id == self.table_id
return objtypes.RecordList(value._row_ids, group_by=value._group_by, sort_by=value._sort_by)
@@ -466,9 +474,24 @@ class ReferenceList(BaseColumnType):
return value is None or (isinstance(value, list) and
all(Reference.is_right_type(val) for val in value))
@classmethod
def typeConvert(cls, value, ref_table, visible_col=None): # noqa # pylint: disable=arguments-differ
# TODO this is based on Reference.typeConvert.
# It doesn't make much sense as a conversion but I don't know what would
if ref_table and visible_col:
return ref_table.lookupRecords(**{visible_col: value}) or six.text_type(value)
else:
return value
class Attachments(ReferenceList):
"""
Currently attachment type is the field for holding data for attachments.
"""
def __init__(self):
super(Attachments, self).__init__('_grist_Attachments')
@classmethod
def typeConvert(cls, value): # noqa # pylint: disable=arguments-differ
# Don't use ReferenceList.typeConvert which is called with a different number of arguments
return value