(core) Record Cards

Summary:
Adds a new Record Card view section to each non-summary table, which can be from opened from various parts of the Grist UI to view and edit records in a popup card view.

Work is still ongoing, so the feature is locked away behind a flag; follow-up work is planned to finish up the implementation and add end-to-end tests.

Test Plan: Python and server tests. Browser tests will be included in a follow-up.

Reviewers: jarek, paulfitz

Reviewed By: jarek

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4114
This commit is contained in:
George Gevoian
2023-11-19 19:46:32 -05:00
parent 2eec48b685
commit caf830db08
53 changed files with 1261 additions and 456 deletions

View File

@@ -141,6 +141,9 @@ class MetaTableExtras(object):
def isRaw(rec, table):
return rec.tableRef.rawViewSectionRef == rec
def isRecordCard(rec, table):
return rec.tableRef.recordCardViewSectionRef == rec
class _grist_Filters(object):
def setAutoRemove(rec, table):
"""Marks the filter for removal if its column no longer exists."""

View File

@@ -1235,3 +1235,57 @@ def migration39(tdset):
if 'description' not in tdset.all_tables['_grist_Views_section'].columns:
doc_actions.append(add_column('_grist_Views_section', 'description', 'Text'))
return tdset.apply_doc_actions(doc_actions)
@migration(schema_version=40)
def migration40(tdset):
"""
Adds a recordCardViewSectionRef column to _grist_Tables, populating it
for each non-summary table in _grist_Tables that has a rawViewSectionRef.
"""
doc_actions = [
add_column(
'_grist_Tables',
'recordCardViewSectionRef',
'Ref:_grist_Views_section'
),
]
tables = list(actions.transpose_bulk_action(tdset.all_tables["_grist_Tables"]))
columns = list(actions.transpose_bulk_action(tdset.all_tables["_grist_Tables_column"]))
new_view_section_id = next_id(tdset, "_grist_Views_section")
for table in sorted(tables, key=lambda t: t.tableId):
if not table.rawViewSectionRef or table.summarySourceTable:
continue
table_columns = [
col for col in columns
if table.id == col.parentId and is_visible_column(col.colId)
]
table_columns.sort(key=lambda c: c.parentPos)
fields = {
"parentId": [new_view_section_id] * len(table_columns),
"colRef": [col.id for col in table_columns],
"parentPos": [col.parentPos for col in table_columns],
}
field_ids = [None] * len(table_columns)
doc_actions += [
actions.AddRecord("_grist_Views_section", new_view_section_id, {
"tableRef": table.id,
"parentId": 0,
"parentKey": "single",
"title": "",
"defaultWidth": 100,
"borderWidth": 1,
}),
actions.UpdateRecord("_grist_Tables", table.id, {
"recordCardViewSectionRef": new_view_section_id,
}),
actions.BulkAddRecord("_grist_Views_section_field", field_ids, fields),
]
new_view_section_id += 1
return tdset.apply_doc_actions(doc_actions)

View File

@@ -15,7 +15,7 @@ import six
import actions
SCHEMA_VERSION = 39
SCHEMA_VERSION = 40
def make_column(col_id, col_type, formula='', isFormula=False):
return {
@@ -58,6 +58,7 @@ def schema_create_actions():
make_column("onDemand", "Bool"),
make_column("rawViewSectionRef", "Ref:_grist_Views_section"),
make_column("recordCardViewSectionRef", "Ref:_grist_Views_section"),
]),
# All columns in all user tables.

View File

@@ -202,7 +202,8 @@ class SummaryActions(object):
encode_summary_table_name(source_table.tableId, groupby_col_ids),
[get_colinfo_dict(ci, with_id=True) for ci in groupby_colinfo + formula_colinfo],
summarySourceTableRef=source_table.id,
raw_section=True)
raw_section=True,
record_card_section=False)
summary_table = self.docmodel.tables.table.get_record(result['id'])
created = True
# Note that in this case, _get_or_add_columns() below should not add any new columns,

View File

@@ -223,15 +223,15 @@ class TestColumnActions(test_engine.EngineTestCase):
Field(2, colRef=12),
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(11, colRef=16),
Field(12, colRef=17),
Section(5, parentKey="record", tableRef=2, fields=[
Field(13, colRef=15),
Field(14, colRef=16),
Field(15, colRef=17),
]),
Section(6, parentKey="record", tableRef=3, fields=[
Field(16, colRef=18),
Field(17, colRef=20),
Field(18, colRef=21),
Section(7, parentKey="record", tableRef=3, fields=[
Field(19, colRef=18),
Field(20, colRef=20),
Field(21, colRef=21),
]),
]),
View(2, sections=[
@@ -311,14 +311,14 @@ class TestColumnActions(test_engine.EngineTestCase):
Field(2, colRef=12),
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(12, colRef=17),
Section(5, parentKey="record", tableRef=2, fields=[
Field(13, colRef=15),
Field(15, colRef=17),
]),
Section(6, parentKey="record", tableRef=3, fields=[
Field(16, colRef=18),
Field(17, colRef=20),
Field(18, colRef=21),
Section(7, parentKey="record", tableRef=3, fields=[
Field(19, colRef=18),
Field(20, colRef=20),
Field(21, colRef=21),
]),
]),
View(2, sections=[
@@ -368,13 +368,13 @@ class TestColumnActions(test_engine.EngineTestCase):
Section(1, parentKey="record", tableRef=1, fields=[
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(12, colRef=17),
Section(5, parentKey="record", tableRef=2, fields=[
Field(13, colRef=15),
Field(15, colRef=17),
]),
Section(6, parentKey="record", tableRef=4, fields=[
Field(17, colRef=23),
Field(18, colRef=24),
Section(7, parentKey="record", tableRef=4, fields=[
Field(20, colRef=23),
Field(21, colRef=24),
]),
]),
View(2, sections=[
@@ -420,14 +420,14 @@ class TestColumnActions(test_engine.EngineTestCase):
self.init_sample_data()
# Add sortSpecs to ViewSections.
self.apply_user_action(['BulkUpdateRecord', '_grist_Views_section', [2, 3, 4],
self.apply_user_action(['BulkUpdateRecord', '_grist_Views_section', [2, 3, 5],
{'sortColRefs': ['[15, -16]', '[-15, 16, 17]', '[19]']}
])
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "sortColRefs" ],
[2, '[15, -16]' ],
[3, '[-15, 16, 17]'],
[4, '[19]' ],
[5, '[19]' ],
])
# Remove column, and check that the correct sortColRefs items are removed.
@@ -436,7 +436,7 @@ class TestColumnActions(test_engine.EngineTestCase):
["id", "sortColRefs"],
[2, '[15]' ],
[3, '[-15, 17]' ],
[4, '[19]' ],
[5, '[19]' ],
])
# Update sortColRefs for next test.
@@ -450,5 +450,5 @@ class TestColumnActions(test_engine.EngineTestCase):
["id", "sortColRefs"],
[2, '[]' ],
[3, '[-16]' ],
[4, '[]' ],
[5, '[]' ],
])

View File

@@ -53,6 +53,7 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 0],
[2, 25, 0],
[3, 25, 0],
])
self.assertTableData("Favorites", cols="subset", data=[
["id", "favorite"],
@@ -70,6 +71,7 @@ class TestUserActions(test_engine.EngineTestCase):
[1, 25, 0],
[2, 25, 0],
[3, 25, 0],
[4, 25, 0],
])
# Set display formula for 'favorite' column.
@@ -86,7 +88,7 @@ class TestUserActions(test_engine.EngineTestCase):
# A single "gristHelper_Display2" column should be added with the requested formula, since both
# require the same formula. The fields' colRefs should be set to the new column.
self.apply_user_action(['SetDisplayFormula', 'Favorites', 1, None, '$favorite.network'])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 3, None, '$favorite.network'])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 4, None, '$favorite.network'])
self.assertTableData("_grist_Tables_column", cols="subset", rows=(lambda r: r.id >= 25), data=[
["id", "colId", "parentId", "displayCol", "formula"],
[25, "favorite", 2, 26, ""],
@@ -97,13 +99,14 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 27],
[2, 25, 0],
[3, 25, 27],
[3, 25, 0],
[4, 25, 27],
])
# Change display formula for a field.
# Since the field is changing to use a formula not yet held by a display column,
# a new display column should be added with the desired formula.
self.apply_user_action(['SetDisplayFormula', 'Favorites', 3, None, '$favorite.viewers'])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 4, None, '$favorite.viewers'])
self.assertTableData("_grist_Tables_column", cols="subset", rows=(lambda r: r.id >= 25), data=[
["id", "colId", "parentId", "displayCol", "formula"],
[25, "favorite", 2, 26, ""],
@@ -115,13 +118,14 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 27],
[2, 25, 0],
[3, 25, 28],
[3, 25, 0],
[4, 25, 28],
])
# Remove a field.
# This should also remove the display column used by that field, since it is not used
# by any other fields.
self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 3])
self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 4])
self.assertTableData("_grist_Tables_column", cols="subset", rows=(lambda r: r.id >= 25), data=[
["id", "colId", "parentId", "displayCol", "formula"],
[25, "favorite", 2, 26, ""],
@@ -132,6 +136,7 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 27],
[2, 25, 0],
[3, 25, 0],
])
# Add a new column with a formula.
@@ -145,7 +150,7 @@ class TestUserActions(test_engine.EngineTestCase):
'parentId': 3,
'colRef': 25
}])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 6, None, '$favorite.viewers'])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 8, None, '$favorite.viewers'])
self.assertTableData("_grist_Tables_column", cols="subset", rows=(lambda r: r.id >= 25), data=[
["id", "colId", "parentId", "displayCol", "formula"],
[25, "favorite", 2, 26, ""],
@@ -158,17 +163,19 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 27],
[2, 25, 0],
[3, 28, 0], # fav_viewers field
[4, 28, 0], # fav_viewers field
[5, 28, 0], # fav_viewers field
[6, 25, 29] # re-added field w/ display col
[3, 25, 0],
[4, 28, 0], # fav_viewers field
[5, 28, 0], # fav_viewers field
[6, 28, 0], # fav_viewers field
[7, 28, 0], # re-added field w/ display col
[8, 25, 29], # fav_viewers field
])
# Change the display formula for a field to be the same as the other field, then remove
# the field.
# The display column should not be removed since it is still in use.
self.apply_user_action(['SetDisplayFormula', 'Favorites', 6, None, '$favorite.network'])
self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 6])
self.apply_user_action(['SetDisplayFormula', 'Favorites', 8, None, '$favorite.network'])
self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 8])
self.assertTableData("_grist_Tables_column", cols="subset", rows=(lambda r: r.id >= 25), data=[
["id", "colId", "parentId", "displayCol", "formula"],
[25, "favorite", 2, 26, ""],
@@ -180,9 +187,11 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 27],
[2, 25, 0],
[3, 28, 0],
[3, 25, 0],
[4, 28, 0],
[5, 28, 0],
[6, 28, 0],
[7, 28, 0],
])
# Clear field display formula, then set it again.
@@ -199,9 +208,11 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 0],
[2, 25, 0],
[3, 28, 0],
[3, 25, 0],
[4, 28, 0],
[5, 28, 0],
[6, 28, 0],
[7, 28, 0],
])
# Setting the display formula should add another display column.
self.apply_user_action(['SetDisplayFormula', 'Favorites', 1, None, '$favorite.viewers'])
@@ -216,9 +227,11 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 29],
[2, 25, 0],
[3, 28, 0],
[3, 25, 0],
[4, 28, 0],
[5, 28, 0],
[6, 28, 0],
[7, 28, 0],
])
# Change column display formula.
@@ -235,9 +248,11 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "colRef", "displayCol"],
[1, 25, 29],
[2, 25, 0],
[3, 28, 0],
[3, 25, 0],
[4, 28, 0],
[5, 28, 0],
[6, 28, 0],
[7, 28, 0],
])
# Remove column.
@@ -249,9 +264,10 @@ class TestUserActions(test_engine.EngineTestCase):
])
self.assertTableData("_grist_Views_section_field", cols="subset", data=[
["id", "colRef", "displayCol"],
[3, 28, 0],
[4, 28, 0],
[5, 28, 0],
[6, 28, 0],
[7, 28, 0],
])
@@ -381,7 +397,7 @@ class TestUserActions(test_engine.EngineTestCase):
# pylint:disable=line-too-long
self.assertOutActions(out_actions, {
"stored": [
["BulkRemoveRecord", "_grist_Views_section_field", [2, 4]],
["BulkRemoveRecord", "_grist_Views_section_field", [2, 4, 6]],
["BulkRemoveRecord", "_grist_Tables_column", [26, 27]],
["RemoveColumn", "People", "favorite"],
["RemoveColumn", "People", "gristHelper_Display"],
@@ -392,7 +408,7 @@ class TestUserActions(test_engine.EngineTestCase):
"undo": [
["BulkUpdateRecord", "People", [1, 2, 3], {"gristHelper_Display2": ["Netflix", "HBO", "NBC"]}],
["BulkUpdateRecord", "People", [1, 2, 3], {"gristHelper_Display": ["Narcos", "Game of Thrones", "Today"]}],
["BulkAddRecord", "_grist_Views_section_field", [2, 4], {"colRef": [26, 26], "displayCol": [28, 0], "parentId": [1, 2], "parentPos": [2.0, 4.0]}],
["BulkAddRecord", "_grist_Views_section_field", [2, 4, 6], {"colRef": [26, 26, 26], "displayCol": [28, 0, 0], "parentId": [1, 2, 3], "parentPos": [2.0, 4.0, 6.0]}],
["BulkAddRecord", "_grist_Tables_column", [26, 27], {"colId": ["favorite", "gristHelper_Display"], "displayCol": [27, 0], "formula": ["", "$favorite.show"], "isFormula": [False, True], "label": ["favorite", "gristHelper_Display"], "parentId": [2, 2], "parentPos": [6.0, 7.0], "type": ["Ref:Television", "Any"], "widgetOptions": ["\"{\"alignment\":\"center\",\"visibleCol\":\"show\"}\"", ""]}],
["BulkUpdateRecord", "People", [1, 2, 3], {"favorite": [12, 11, 13]}],
["AddColumn", "People", "favorite", {"formula": "", "isFormula": False, "type": "Ref:Television"}],

View File

@@ -141,35 +141,42 @@ class TestDocModel(test_engine.EngineTestCase):
self.assertPartialData('_grist_Views_section', ["id", "parentId", "tableRef"], [
[1, 1, 4],
[2, 0, 4],
[3, 2, 5],
[4, 0, 5],
[5, 1, 4],
[6, 1, 5],
[3, 0, 4],
[4, 2, 5],
[5, 0, 5],
[6, 0, 5],
[7, 1, 4],
[8, 1, 5],
])
self.assertPartialData('_grist_Views_section_field', ["id", "parentId", "parentPos"], [
[1, 1, 1.0],
[2, 1, 2.0],
[3, 2, 3.0],
[4, 2, 4.0],
[5, 3, 5.0],
[6, 3, 6.0],
[7, 4, 7.0],
[8, 4, 8.0],
[9, 5, 9.0],
[1, 1, 1.0],
[2, 1, 2.0],
[3, 2, 3.0],
[4, 2, 4.0],
[5, 3, 5.0],
[6, 3, 6.0],
[7, 4, 7.0],
[8, 4, 8.0],
[9, 5, 9.0],
[10, 5, 10.0],
[11, 6, 11.0],
[12, 6, 12.0],
[13, 7, 13.0],
[14, 7, 14.0],
[15, 8, 15.0],
[16, 8, 16.0],
])
table = self.engine.docmodel.tables.lookupOne(tableId='Test2')
self.assertRecordSet(table.viewSections, [1, 2, 5])
self.assertRecordSet(table.viewSections, [1, 2, 3, 7])
self.assertRecordSet(list(table.viewSections)[0].fields, [1, 2])
self.assertRecordSet(list(table.viewSections)[2].fields, [9, 10])
self.assertRecordSet(list(table.viewSections)[3].fields, [13, 14])
view = self.engine.docmodel.views.lookupOne(id=1)
self.assertRecordSet(view.viewSections, [1, 5, 6])
self.assertRecordSet(view.viewSections, [1, 7, 8])
self.engine.docmodel.remove(set(table.viewSections) - {table.rawViewSectionRef})
self.assertRecordSet(view.viewSections, [6])
self.engine.docmodel.remove(set(table.viewSections) -
{table.rawViewSectionRef, table.recordCardViewSectionRef})
self.assertRecordSet(view.viewSections, [8])
def test_modifications(self):

View File

@@ -54,10 +54,13 @@ class TestImportActions(test_engine.EngineTestCase):
self.assertPartialData("_grist_Views_section", ["id", "tableRef", 'fields'], [
[1, 1, [1, 2, 3]], # section for "Source" table
[2, 1, [4, 5, 6]], # section for "Source" table
[3, 2, [7, 8]], # section for "Destination1" table
[4, 2, [9, 10]], # section for "Destination1" table
[5, 3, [11]], # section for "Destination2" table
[6, 3, [12]], # section for "Destination2" table
[3, 1, [7, 8, 9]], # section for "Source" table
[4, 2, [10, 11]], # section for "Destination1" table
[5, 2, [12, 13]], # section for "Destination1" table
[6, 2, [14, 15]], # section for "Destination1" table
[7, 3, [16]], # section for "Destination2" table
[8, 3, [17]], # section for "Destination2" table
[9, 3, [18]], # section for "Destination2" table
])
def test_transform(self):
@@ -89,11 +92,14 @@ class TestImportActions(test_engine.EngineTestCase):
self.assertPartialData("_grist_Views_section", ["id", "tableRef", 'fields'], [
[1, 1, [1, 2, 3]],
[2, 1, [4, 5, 6]],
[3, 2, [7, 8]],
[4, 2, [9, 10]],
[5, 3, [11]],
[6, 3, [12]],
[7, 1, [13, 14]], # new section for transform preview
[3, 1, [7, 8, 9]],
[4, 2, [10, 11]],
[5, 2, [12, 13]],
[6, 2, [14, 15]],
[7, 3, [16]],
[8, 3, [17]],
[9, 3, [18]],
[10, 1, [19, 20]], # new section for transform preview
])
# Apply useraction again to verify that old columns and sections are removing
@@ -117,17 +123,20 @@ class TestImportActions(test_engine.EngineTestCase):
[2, "Alison", "Boston", 7003, "", 2.0],
])
self.assertPartialData("_grist_Views_section", ["id", "tableRef", 'fields'], [
[1, 1, [1, 2, 3]],
[2, 1, [4, 5, 6]],
[3, 2, [7, 8]],
[4, 2, [9, 10]],
[5, 3, [11]],
[6, 3, [12]],
[7, 1, [13]], # new section for transform preview
[1, 1, [1, 2, 3]],
[2, 1, [4, 5, 6]],
[3, 1, [7, 8, 9]],
[4, 2, [10, 11]],
[5, 2, [12, 13]],
[6, 2, [14, 15]],
[7, 3, [16]],
[8, 3, [17]],
[9, 3, [18]],
[10, 1, [19]], # new section for transform preview
])
def test_regenereate_importer_view(self):
def test_regenerate_importer_view(self):
# Generate without a destination table, and then with one. Ensure that we don't omit the
# actions needed to populate the table in the second call.
self.init_state()
@@ -135,8 +144,8 @@ class TestImportActions(test_engine.EngineTestCase):
out_actions = self.apply_user_action(['GenImporterView', 'Source', 'Destination1', None, {}])
self.assertPartialOutActions(out_actions, {
"stored": [
["BulkRemoveRecord", "_grist_Views_section_field", [13, 14, 15]],
["RemoveRecord", "_grist_Views_section", 7],
["BulkRemoveRecord", "_grist_Views_section_field", [19, 20, 21]],
["RemoveRecord", "_grist_Views_section", 10],
["BulkRemoveRecord", "_grist_Tables_column", [10, 11, 12]],
["RemoveColumn", "Source", "gristHelper_Import_Name"],
["RemoveColumn", "Source", "gristHelper_Import_City"],
@@ -145,8 +154,8 @@ class TestImportActions(test_engine.EngineTestCase):
["AddRecord", "_grist_Tables_column", 10, {"colId": "gristHelper_Import_Name", "formula": "$Name", "isFormula": True, "label": "Name", "parentId": 1, "parentPos": 10.0, "type": "Text", "widgetOptions": ""}],
["AddColumn", "Source", "gristHelper_Import_City", {"formula": "$City", "isFormula": True, "type": "Text"}],
["AddRecord", "_grist_Tables_column", 11, {"colId": "gristHelper_Import_City", "formula": "$City", "isFormula": True, "label": "City", "parentId": 1, "parentPos": 11.0, "type": "Text", "widgetOptions": ""}],
["AddRecord", "_grist_Views_section", 7, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "sortColRefs": "[]", "tableRef": 1}],
["BulkAddRecord", "_grist_Views_section_field", [13, 14], {"colRef": [10, 11], "parentId": [7, 7], "parentPos": [13.0, 14.0]}],
["AddRecord", "_grist_Views_section", 10, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "sortColRefs": "[]", "tableRef": 1}],
["BulkAddRecord", "_grist_Views_section_field", [19, 20], {"colRef": [10, 11], "parentId": [10, 10], "parentPos": [19.0, 20.0]}],
# The actions to populate the removed and re-added columns should be there.
["BulkUpdateRecord", "Source", [1, 2], {"gristHelper_Import_City": ["New York", "Boston"]}],
["BulkUpdateRecord", "Source", [1, 2], {"gristHelper_Import_Name": ["John", "Alison"]}],
@@ -181,11 +190,14 @@ class TestImportActions(test_engine.EngineTestCase):
[2, "Alison", "Boston", 7003, "Alison", "Boston", 7003, 2.0],
])
self.assertPartialData("_grist_Views_section", ["id", "tableRef", 'fields'], [
[1, 1, [1, 2, 3]],
[2, 1, [4, 5, 6]],
[3, 2, [7, 8]],
[4, 2, [9, 10]],
[5, 3, [11]],
[6, 3, [12]],
[7, 1, [13, 14, 15]], # new section for transform preview
[1, 1, [1, 2, 3]],
[2, 1, [4, 5, 6]],
[3, 1, [7, 8, 9]],
[4, 2, [10, 11]],
[5, 2, [12, 13]],
[6, 2, [14, 15]],
[7, 3, [16]],
[8, 3, [17]],
[9, 3, [18]],
[10, 1, [19, 20, 21]], # new section for transform preview
])

View File

@@ -88,27 +88,27 @@ class TestTableActions(test_engine.EngineTestCase):
]),
]),
View(2, sections=[
Section(3, parentKey="record", tableRef=2, fields=[
Field(7, colRef=6),
Field(8, colRef=7),
Field(9, colRef=8),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=6),
Field(11, colRef=7),
Field(12, colRef=8),
]),
]),
View(3, sections=[
Section(5, parentKey="record", tableRef=1, fields=[
Field(13, colRef=2),
Field(14, colRef=3),
Field(15, colRef=4),
Section(7, parentKey="record", tableRef=1, fields=[
Field(19, colRef=2),
Field(20, colRef=3),
Field(21, colRef=4),
]),
Section(7, parentKey="record", tableRef=3, fields=[
Field(19, colRef=9),
Field(20, colRef=11),
Field(21, colRef=12),
Section(9, parentKey="record", tableRef=3, fields=[
Field(25, colRef=9),
Field(26, colRef=11),
Field(27, colRef=12),
]),
Section(8, parentKey="record", tableRef=2, fields=[
Field(22, colRef=6),
Field(23, colRef=7),
Field(24, colRef=8),
Section(10, parentKey="record", tableRef=2, fields=[
Field(28, colRef=6),
Field(29, colRef=7),
Field(30, colRef=8),
]),
]),
])
@@ -295,17 +295,17 @@ class TestTableActions(test_engine.EngineTestCase):
])
self.assertViews([
View(2, sections=[
Section(3, parentKey="record", tableRef=2, fields=[
Field(7, colRef=6),
Field(8, colRef=7),
Field(9, colRef=8),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=6),
Field(11, colRef=7),
Field(12, colRef=8),
]),
]),
View(3, sections=[
Section(8, parentKey="record", tableRef=2, fields=[
Field(22, colRef=6),
Field(23, colRef=7),
Field(24, colRef=8),
Section(10, parentKey="record", tableRef=2, fields=[
Field(28, colRef=6),
Field(29, colRef=7),
Field(30, colRef=8),
]),
]),
])

View File

@@ -58,6 +58,7 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertPartialData("_grist_Views_section_field", ["id", "colRef", "widgetOptions"], [
[1, 23, ""],
[2, 23, ""],
[3, 23, ""],
])
self.assertPartialData("Schools", ["id", "city"], [
[1, "New York" ],
@@ -78,8 +79,11 @@ class TestUserActions(test_engine.EngineTestCase):
'grist_Transform', 'formula': 'return $city', 'label': 'grist_Transform',
'type': 'Text'
}],
["AddRecord", "_grist_Views_section_field", 3, {
"colRef": 24, "parentId": 2, "parentPos": 3.0
["AddRecord", "_grist_Views_section_field", 4, {
"colRef": 24, "parentId": 2, "parentPos": 4.0
}],
["AddRecord", "_grist_Views_section_field", 5, {
"colRef": 24, "parentId": 3, "parentPos": 5.0
}],
["BulkUpdateRecord", "Schools", [1, 2, 3],
{"grist_Transform": ["New York", "Colombia", "New York"]}],
@@ -122,7 +126,7 @@ class TestUserActions(test_engine.EngineTestCase):
out_actions = self.remove_column('Schools', 'grist_Transform')
self.assertPartialOutActions(out_actions, { "stored": [
["RemoveRecord", "_grist_Views_section_field", 3],
["BulkRemoveRecord", "_grist_Views_section_field", [4, 5]],
['RemoveRecord', '_grist_Tables_column', 24],
['RemoveColumn', 'Schools', 'grist_Transform'],
]})
@@ -205,10 +209,10 @@ class TestUserActions(test_engine.EngineTestCase):
Column(25, "C", "Any", isFormula=True, formula="", summarySourceCol=0),
])
new_view = View(1, sections=[
Section(2, parentKey="record", tableRef=2, fields=[
Field(4, colRef=23),
Field(5, colRef=24),
Field(6, colRef=25),
Section(3, parentKey="record", tableRef=2, fields=[
Field(7, colRef=23),
Field(8, colRef=24),
Field(9, colRef=25),
])
])
self.assertTables([self.starting_table, new_table])
@@ -223,10 +227,10 @@ class TestUserActions(test_engine.EngineTestCase):
Column(29, "C", "Any", isFormula=True, formula="", summarySourceCol=0),
])
new_view.sections.append(
Section(4, parentKey="record", tableRef=3, fields=[
Field(10, colRef=27),
Field(11, colRef=28),
Field(12, colRef=29),
Section(6, parentKey="record", tableRef=3, fields=[
Field(16, colRef=27),
Field(17, colRef=28),
Field(18, colRef=29),
])
)
# Check that we have a new table, only the new view; and a new section.
@@ -256,8 +260,8 @@ class TestUserActions(test_engine.EngineTestCase):
Column(35, "count", "Int", isFormula=True, formula="len($group)", summarySourceCol=0),
])
self.assertTables([self.starting_table, new_table, new_table2, new_table3, summary_table])
new_view.sections.append(Section(7, parentKey="record", tableRef=5, fields=[
Field(17, colRef=35)
new_view.sections.append(Section(10, parentKey="record", tableRef=5, fields=[
Field(26, colRef=35)
]))
self.assertViews([new_view])
@@ -311,26 +315,26 @@ class TestUserActions(test_engine.EngineTestCase):
]),
]),
View(2, sections=[
Section(3, parentKey="detail", tableRef=1, fields=[
Field(7, colRef=2),
Field(8, colRef=3),
Field(9, colRef=4),
Section(4, parentKey="detail", tableRef=1, fields=[
Field(10, colRef=2),
Field(11, colRef=3),
Field(12, colRef=4),
]),
Section(5, parentKey="record", tableRef=2, fields=[
Field(13, colRef=5),
Field(14, colRef=7),
Field(15, colRef=8),
Section(6, parentKey="record", tableRef=2, fields=[
Field(16, colRef=5),
Field(17, colRef=7),
Field(18, colRef=8),
]),
Section(8, parentKey='record', tableRef=3, fields=[
Field(21, colRef=10),
Field(22, colRef=11),
Field(23, colRef=12),
Section(10, parentKey='record', tableRef=3, fields=[
Field(27, colRef=10),
Field(28, colRef=11),
Field(29, colRef=12),
]),
]),
View(3, sections=[
Section(6, parentKey="chart", tableRef=1, fields=[
Field(16, colRef=2),
Field(17, colRef=3),
Section(7, parentKey="chart", tableRef=1, fields=[
Field(19, colRef=2),
Field(20, colRef=3),
]),
])
])
@@ -468,10 +472,10 @@ class TestUserActions(test_engine.EngineTestCase):
{'title': 'Z'}])
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
[1, 'Z', 1, 2],
[2, 'Z_summary_state', 0, 4],
[3, 'Table1', 0, 7],
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef', 'recordCardViewSectionRef'],
[1, 'Z', 1, 2, 3],
[2, 'Z_summary_state', 0, 5, 0],
[3, 'Table1', 0, 8, 9],
])
self.assertTableData('_grist_Views', cols="subset", data=[
['id', 'name'],
@@ -485,11 +489,11 @@ class TestUserActions(test_engine.EngineTestCase):
{'id': 'city', 'type': 'Text'},
]])
self.assertTableData('_grist_Tables', cols="subset", data=[
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
[1, 'Z', 1, 2],
[2, 'Z_summary_state', 0, 4],
[3, 'Table1', 0, 7],
[4, 'Stations', 4, 10],
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef', 'recordCardViewSectionRef'],
[1, 'Z', 1, 2, 3],
[2, 'Z_summary_state', 0, 5, 0],
[3, 'Table1', 0, 8, 9],
[4, 'Stations', 4, 12, 13],
])
self.assertTableData('_grist_Views', cols="subset", data=[
['id', 'name'],
@@ -542,32 +546,32 @@ class TestUserActions(test_engine.EngineTestCase):
]),
]),
View(2, sections=[
Section(3, parentKey="detail", tableRef=1, fields=[
Field(7, colRef=2),
Field(8, colRef=3),
Field(9, colRef=4),
Section(4, parentKey="detail", tableRef=1, fields=[
Field(10, colRef=2),
Field(11, colRef=3),
Field(12, colRef=4),
]),
Section(5, parentKey="record", tableRef=2, fields=[
Field(13, colRef=5),
Field(14, colRef=7),
Field(15, colRef=8),
Section(6, parentKey="record", tableRef=2, fields=[
Field(16, colRef=5),
Field(17, colRef=7),
Field(18, colRef=8),
]),
Section(8, parentKey='record', tableRef=3, fields=[
Field(21, colRef=10),
Field(22, colRef=11),
Field(23, colRef=12),
Section(10, parentKey='record', tableRef=3, fields=[
Field(27, colRef=10),
Field(28, colRef=11),
Field(29, colRef=12),
]),
]),
View(3, sections=[
Section(6, parentKey="chart", tableRef=1, fields=[
Field(16, colRef=2),
Field(17, colRef=3),
Section(7, parentKey="chart", tableRef=1, fields=[
Field(19, colRef=2),
Field(20, colRef=3),
]),
])
])
# Remove a couple of sections. Ensure their fields get removed.
self.apply_user_action(['BulkRemoveRecord', '_grist_Views_section', [5, 8]])
self.apply_user_action(['BulkRemoveRecord', '_grist_Views_section', [6, 10]])
self.assertViews([
View(1, sections=[
@@ -578,16 +582,16 @@ class TestUserActions(test_engine.EngineTestCase):
]),
]),
View(2, sections=[
Section(3, parentKey="detail", tableRef=1, fields=[
Field(7, colRef=2),
Field(8, colRef=3),
Field(9, colRef=4),
Section(4, parentKey="detail", tableRef=1, fields=[
Field(10, colRef=2),
Field(11, colRef=3),
Field(12, colRef=4),
])
]),
View(3, sections=[
Section(6, parentKey="chart", tableRef=1, fields=[
Field(16, colRef=2),
Field(17, colRef=3),
Section(7, parentKey="chart", tableRef=1, fields=[
Field(19, colRef=2),
Field(20, colRef=3),
]),
])
])
@@ -613,8 +617,8 @@ class TestUserActions(test_engine.EngineTestCase):
self.assertEqual(count_calls[0], 0)
# Do a schema action to ensure it gets called: this causes a table rename.
# 7 is id of raw view section for the Tabl1 table
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 7, {'title': 'C'}])
# 8 is id of raw view section for the Table1 table
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 8, {'title': 'C'}])
self.assertEqual(count_calls[0], 1)
self.assertTableData('_grist_Tables', cols="subset", data=[
@@ -1403,6 +1407,7 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "parentId", "tableRef"],
[1, 1, 2],
[2, 0, 2], # the raw view section
[3, 0, 2], # the record card view section
])
self.assertTableData('_grist_Views_section_field', cols="subset", data=[
["id", "parentId"],
@@ -1414,6 +1419,11 @@ class TestUserActions(test_engine.EngineTestCase):
[4, 2],
[5, 2],
[6, 2],
# the record card view section
[7, 3],
[8, 3],
[9, 3],
])
# Test that the records cannot be removed by normal user actions
@@ -1433,6 +1443,7 @@ class TestUserActions(test_engine.EngineTestCase):
["id", "parentId", "tableRef"],
[1, 1, 2],
[2, 0, 2], # the raw view section
[3, 0, 2], # the record card view section
])
self.assertTableData('_grist_Views_section_field', cols="subset", data=[
["id", "parentId"],
@@ -1444,6 +1455,45 @@ class TestUserActions(test_engine.EngineTestCase):
[4, 2],
[5, 2],
[6, 2],
# the record card view section
[7, 3],
[8, 3],
[9, 3],
])
def test_record_card_view_section_restrictions(self):
self.load_sample(self.sample)
self.apply_user_action(["AddEmptyTable", None])
# Check that record card view sections cannot be removed by normal user actions.
with self.assertRaisesRegex(ValueError, "Cannot remove record card view section$"):
self.apply_user_action(["RemoveRecord", '_grist_Views_section', 3])
# Check that most of their column values can't be changed.
with self.assertRaisesRegex(ValueError, "Cannot modify record card view section$"):
self.apply_user_action(["UpdateRecord", '_grist_Views_section', 3, {"parentId": 1}])
with self.assertRaisesRegex(ValueError, "Cannot modify record card view section fields$"):
self.apply_user_action(["UpdateRecord", '_grist_Views_section_field', 9, {"parentId": 1}])
# Make sure nothing got removed or updated.
self.assertTableData('_grist_Views_section', cols="subset", data=[
["id", "parentId", "tableRef"],
[1, 1, 2],
[2, 0, 2],
[3, 0, 2],
])
self.assertTableData('_grist_Views_section_field', cols="subset", data=[
["id", "parentId"],
[1, 1],
[2, 1],
[3, 1],
[4, 2],
[5, 2],
[6, 2],
[7, 3],
[8, 3],
[9, 3],
])
def test_update_current_time(self):

View File

@@ -918,7 +918,12 @@
// Raw data widget
["AddRecord", "_grist_Views_section", 2, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 4, "title": ""}],
["AddRecord", "_grist_Views_section_field", 2, {"colRef": 34, "parentId": 2, "parentPos": 2.0}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2}],
// Record card widget
["AddRecord", "_grist_Views_section", 3, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "single", "tableRef": 4, "title": ""}],
["AddRecord", "_grist_Views_section_field", 3, {"colRef": 34, "parentId": 3, "parentPos": 3.0}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2, "recordCardViewSectionRef": 3}],
// Actions generated from AddColumn.
["AddColumn", "Bar", "world",
@@ -927,9 +932,10 @@
{"colId": "world", "parentPos": 13.0,
"formula": "rec.hello.upper()", "parentId": 4, "type": "Text",
"isFormula": true, "label": "world", "widgetOptions": ""}],
["AddRecord", "_grist_Views_section_field", 3, {"colRef": 35, "parentId": 2, "parentPos": 3.0}]
["AddRecord", "_grist_Views_section_field", 4, {"colRef": 35, "parentId": 2, "parentPos": 4.0}],
["AddRecord", "_grist_Views_section_field", 5, {"colRef": 35, "parentId": 3, "parentPos": 5.0}]
],
"direct": [true, true, true, true,
"direct": [true, true, true, true, true, true, true,
true, true, true, true, true, true, true,
true, true, true],
"undo": [
@@ -943,10 +949,13 @@
["RemoveRecord", "_grist_Views_section_field", 1],
["RemoveRecord", "_grist_Views_section", 2],
["RemoveRecord", "_grist_Views_section_field", 2],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0}],
["RemoveRecord", "_grist_Views_section", 3],
["RemoveRecord", "_grist_Views_section_field", 3],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0, "recordCardViewSectionRef": 0}],
["RemoveColumn", "Bar", "world"],
["RemoveRecord", "_grist_Tables_column", 35],
["RemoveRecord", "_grist_Views_section_field", 3]
["RemoveRecord", "_grist_Views_section_field", 4],
["RemoveRecord", "_grist_Views_section_field", 5]
],
"retValue": [
{
@@ -1257,15 +1266,16 @@
{"parentId": [1,1], "colRef": [31,32], "parentPos": [1.0,2.0]}],
["AddRecord", "_grist_Views_section", 2, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 4, "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [3, 4], {"colRef": [31, 32], "parentId": [2, 2], "parentPos": [3.0, 4.0]}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2}],
["BulkRemoveRecord", "_grist_Views_section_field", [1, 3]],
["AddRecord", "_grist_Views_section", 3, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "single", "tableRef": 4, "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [5, 6], {"colRef": [31, 32], "parentId": [3, 3], "parentPos": [5.0, 6.0]}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2, "recordCardViewSectionRef": 3}],
["BulkRemoveRecord", "_grist_Views_section_field", [1, 3, 5]],
["RemoveRecord", "_grist_Tables_column", 31],
["RemoveColumn", "ViewTest", "hello"]
],
"direct": [true, true, true, true, true, true, true, true, true,
true, true,
true, true, true],
true, true, true, true, true, true, true],
"undo": [
["RemoveTable", "ViewTest"],
["RemoveRecord", "_grist_Tables", 4],
@@ -1277,8 +1287,11 @@
["BulkRemoveRecord", "_grist_Views_section_field", [1,2]],
["RemoveRecord", "_grist_Views_section", 2],
["BulkRemoveRecord", "_grist_Views_section_field", [3, 4]],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0}],
["BulkAddRecord", "_grist_Views_section_field", [1, 3], {"colRef": [31, 31], "parentId": [1, 2], "parentPos": [1.0, 3.0]}],
["RemoveRecord", "_grist_Views_section", 3],
["BulkRemoveRecord", "_grist_Views_section_field", [5, 6]],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0, "recordCardViewSectionRef": 0}],
["BulkAddRecord", "_grist_Views_section_field", [1, 3, 5],
{"colRef": [31, 31, 31], "parentId": [1, 2, 3], "parentPos": [1.0, 3.0, 5.0]}],
["AddRecord", "_grist_Tables_column", 31,
{"colId": "hello", "parentPos": 9.0,
"parentId": 4, "type": "Text"
@@ -2199,7 +2212,8 @@
{"tableRef": 4, "defaultWidth": 100, "borderWidth": 1,
"parentId": 1, "parentKey": "record", "sortColRefs": "[]", "title": ""}],
["AddRecord", "_grist_Views_section", 2, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 4, "title": ""}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2}],
["AddRecord", "_grist_Views_section", 3, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "single", "tableRef": 4, "title": ""}],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2, "recordCardViewSectionRef": 3}],
["AddTable", "Bar", [
{"id": "manualSort", "formula": "", "isFormula": false, "type": "ManualSortPos"},
{"isFormula": false, "formula": "", "type": "Text", "id": "hello"},
@@ -2223,21 +2237,23 @@
{"type": "raw_data", "name": "Bar"}],
["AddRecord", "_grist_TabBar", 2, {"tabPos": 2.0, "viewRef": 2}],
["AddRecord", "_grist_Pages", 2, {"pagePos": 2.0, "viewRef": 2, "indentation": 0}],
["AddRecord", "_grist_Views_section", 3,
["AddRecord", "_grist_Views_section", 4,
{"tableRef": 5, "defaultWidth": 100, "borderWidth": 1,
"parentId": 2, "parentKey": "record", "sortColRefs": "[]", "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [1,2,3],
{"parentId": [3,3,3], "colRef": [32,33,34], "parentPos": [1.0,2.0,3.0]}],
["AddRecord", "_grist_Views_section", 4, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 5, "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [4, 5, 6], {"colRef": [32, 33, 34], "parentId": [4, 4, 4], "parentPos": [4.0, 5.0, 6.0]}],
["UpdateRecord", "_grist_Tables", 5, {"primaryViewId": 2, "rawViewSectionRef": 4}],
{"parentId": [4,4,4], "colRef": [32,33,34], "parentPos": [1.0,2.0,3.0]}],
["AddRecord", "_grist_Views_section", 5, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 5, "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [4, 5, 6], {"colRef": [32, 33, 34], "parentId": [5, 5, 5], "parentPos": [4.0, 5.0, 6.0]}],
["AddRecord", "_grist_Views_section", 6, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "single", "tableRef": 5, "title": ""}],
["BulkAddRecord", "_grist_Views_section_field", [7, 8, 9], {"colRef": [32, 33, 34], "parentId": [6, 6, 6], "parentPos": [7.0, 8.0, 9.0]}],
["UpdateRecord", "_grist_Tables", 5, {"primaryViewId": 2, "rawViewSectionRef": 5, "recordCardViewSectionRef": 6}],
["AddRecord", "Bar", 1, {"foo": 0, "hello": "a", "manualSort": 1.0}],
["AddRecord", "Bar", 2, {"foo": 1, "hello": "b", "manualSort": 2.0}],
["AddRecord", "Bar", 3, {"foo": 1, "hello": "c", "manualSort": 3.0}],
["BulkUpdateRecord", "Bar", [1, 2, 3], {"world": ["A", "B", "C"]}]
],
"direct": [true, true, true, true, true, true, true, true,
true, true, true,
true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true,
true, true, true, false],
"undo": [
@@ -2249,18 +2265,21 @@
["RemoveRecord", "_grist_Pages", 1],
["RemoveRecord", "_grist_Views_section", 1],
["RemoveRecord", "_grist_Views_section", 2],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0}],
["RemoveRecord", "_grist_Views_section", 3],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0, "recordCardViewSectionRef": 0}],
["RemoveTable", "Bar"],
["RemoveRecord", "_grist_Tables", 5],
["BulkRemoveRecord", "_grist_Tables_column", [31,32,33,34]],
["RemoveRecord", "_grist_Views", 2],
["RemoveRecord", "_grist_TabBar", 2],
["RemoveRecord", "_grist_Pages", 2],
["RemoveRecord", "_grist_Views_section", 3],
["BulkRemoveRecord", "_grist_Views_section_field", [1,2,3]],
["RemoveRecord", "_grist_Views_section", 4],
["BulkRemoveRecord", "_grist_Views_section_field", [1,2,3]],
["RemoveRecord", "_grist_Views_section", 5],
["BulkRemoveRecord", "_grist_Views_section_field", [4, 5, 6]],
["UpdateRecord", "_grist_Tables", 5, {"primaryViewId": 0, "rawViewSectionRef": 0}],
["RemoveRecord", "_grist_Views_section", 6],
["BulkRemoveRecord", "_grist_Views_section_field", [7, 8, 9]],
["UpdateRecord", "_grist_Tables", 5, {"primaryViewId": 0, "rawViewSectionRef": 0, "recordCardViewSectionRef": 0}],
["RemoveRecord", "Bar", 1],
["RemoveRecord", "Bar", 2],
["RemoveRecord", "Bar", 3]
@@ -2281,7 +2300,7 @@
"id": 5,
"columns": ["hello", "world", "foo"],
"views": [
{ "sections": [ 3 ], "id": 2 }
{ "sections": [ 4 ], "id": 2 }
]
},
// AddRecord retValues
@@ -2333,10 +2352,12 @@
"parentId": 1, "parentKey": "record", "sortColRefs": "[]", "title": ""}],
// Raw data widget
["AddRecord", "_grist_Views_section", 2, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "record", "tableRef": 4, "title": ""}],
// Record card widget
["AddRecord", "_grist_Views_section", 3, {"borderWidth": 1, "defaultWidth": 100, "parentKey": "single", "tableRef": 4, "title": ""}],
// As part of adding a table, we also set the primaryViewId.
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2}]
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 1, "rawViewSectionRef": 2, "recordCardViewSectionRef": 3}]
],
"direct": [true, true, true, true, true, true, true, true, true],
"direct": [true, true, true, true, true, true, true, true, true, true],
"undo": [
["RemoveTable", "Foo"],
["RemoveRecord", "_grist_Tables", 4],
@@ -2346,7 +2367,8 @@
["RemoveRecord", "_grist_Pages", 1],
["RemoveRecord", "_grist_Views_section", 1],
["RemoveRecord", "_grist_Views_section", 2],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0}]
["RemoveRecord", "_grist_Views_section", 3],
["UpdateRecord", "_grist_Tables", 4, {"primaryViewId": 0, "rawViewSectionRef": 0, "recordCardViewSectionRef": 0}]
]
}
}],
@@ -2359,7 +2381,8 @@
"USER_ACTION": ["RemoveTable", "Foo"],
"ACTIONS": {
"stored": [
["BulkRemoveRecord", "_grist_Views_section", [1, 2]],
["BulkRemoveRecord", "_grist_Views_section", [1, 2, 3]],
["UpdateRecord", "_grist_Tables", 4, {"recordCardViewSectionRef": 0}],
["UpdateRecord", "_grist_Tables", 4, {"rawViewSectionRef": 0}],
["RemoveRecord", "_grist_TabBar", 1],
["RemoveRecord", "_grist_Pages", 1],
@@ -2369,11 +2392,12 @@
["RemoveRecord", "_grist_Tables", 4],
["RemoveTable", "Foo"]
],
"direct": [true, true, true, true, true, true, true, true, true],
"direct": [true, true, true, true, true, true, true, true, true, true],
"undo": [
["BulkAddRecord", "_grist_Views_section", [1, 2],
{"borderWidth": [1, 1], "defaultWidth": [100, 100], "parentId": [1, 0],
"parentKey": ["record", "record"], "sortColRefs": ["[]", ""], "tableRef": [4, 4]}],
["BulkAddRecord", "_grist_Views_section", [1, 2, 3],
{"borderWidth": [1, 1, 1], "defaultWidth": [100, 100, 100], "parentId": [1, 0, 0],
"parentKey": ["record", "record", "single"], "sortColRefs": ["[]", "", ""], "tableRef": [4, 4, 4]}],
["UpdateRecord", "_grist_Tables", 4, {"recordCardViewSectionRef": 3}],
["UpdateRecord", "_grist_Tables", 4, {"rawViewSectionRef": 2}],
["AddRecord", "_grist_TabBar", 1, {"tabPos": 1.0, "viewRef": 1}],
["AddRecord", "_grist_Pages", 1, {"pagePos": 1.0, "viewRef": 1}],

View File

@@ -479,6 +479,24 @@ class UserActions(object):
):
raise ValueError("Cannot modify raw view section fields")
# Prevent modifying record card widgets and their fields.
if (
table_id == "_grist_Views_section"
and any(rec.isRecordCard for i, rec in self._bulk_action_iter(table_id, row_ids))
):
allowed_fields = {"layoutSpec", "options", "theme"}
if not set(column_values) <= allowed_fields:
raise ValueError("Cannot modify record card view section")
if (
table_id == "_grist_Views_section_field"
and any(rec.parentId.isRecordCard for i, rec in self._bulk_action_iter(table_id, row_ids))
and not set(column_values) <= {
"displayCol", "parentPos", "rules", "visibleCol", "widgetOptions"
}
):
raise ValueError("Cannot modify record card view section fields")
# If any extra actions were generated (e.g. to adjust positions), apply them.
for a in extra_actions:
self._do_doc_action(a)
@@ -1300,13 +1318,15 @@ class UserActions(object):
def _removeViewSectionRecords(self, table_id, row_ids):
"""
Remove view sections, including their fields.
Raises an error if trying to remove a table's rawViewSectionRef.
Raises an error if trying to remove a table's rawViewSectionRef or recordCardViewSectionRef.
To bypass that check, call _doRemoveViewSectionRecords.
"""
recs = [rec for i, rec in self._bulk_action_iter(table_id, row_ids)]
for rec in recs:
if rec.isRaw:
raise ValueError("Cannot remove raw view section")
if rec.isRecordCard:
raise ValueError("Cannot remove record card view section")
self._doRemoveViewSectionRecords(recs)
def _doRemoveViewSectionRecords(self, recs):
@@ -1355,16 +1375,35 @@ class UserActions(object):
ret = self.doAddColumn(table_id, col_id, col_info)
if not transform and table_rec.rawViewSectionRef:
# Add a field for this column to the "raw_data" section for this table.
# TODO: the position of the inserted field or of the inserted column will often be
# bogus, since fields and columns are not the same. This requires better coordination
# with the client-side.
self._docmodel.insert(
table_rec.rawViewSectionRef.fields,
col_info.get('_position'),
colRef=ret['colRef']
)
if not transform:
if table_rec.rawViewSectionRef:
# Add a field for this column to the "raw_data" section for this table.
# TODO: the position of the inserted field or of the inserted column will often be
# bogus, since fields and columns are not the same. This requires better coordination
# with the client-side.
self._docmodel.insert(
table_rec.rawViewSectionRef.fields,
col_info.get('_position'),
colRef=ret['colRef']
)
if table_rec.recordCardViewSectionRef:
# If the record card section or one of its fields hasn't yet been modified,
# add a field for this column.
section = table_rec.recordCardViewSectionRef
modified = (
section.layoutSpec or
section.options or
section.rules or
section.theme or
any(f.widgetOptions for f in section.fields)
)
if not modified:
self._docmodel.insert(
table_rec.recordCardViewSectionRef.fields,
col_info.get('_position'),
colRef=ret['colRef']
)
return ret
@@ -1807,7 +1846,8 @@ class UserActions(object):
columns,
manual_sort=True,
primary_view=True,
raw_section=True)
raw_section=True,
record_card_section=True)
@useraction
@@ -1821,12 +1861,14 @@ class UserActions(object):
columns,
manual_sort=True,
primary_view=False,
raw_section=True
raw_section=True,
record_card_section=True
)
def doAddTable(self, table_id, columns, manual_sort=False, primary_view=False,
raw_section=False, summarySourceTableRef=0):
raw_section=False, record_card_section=False,
summarySourceTableRef=0):
"""
Add the given table with columns with or without additional views.
"""
@@ -1887,10 +1929,20 @@ class UserActions(object):
table_title if not summarySourceTableRef else ""
)
if primary_view or raw_section:
if record_card_section:
record_card_section = self.create_plain_view_section(
result["id"],
table_id,
self._docmodel.view_sections,
"single",
""
)
if primary_view or raw_section or record_card_section:
self.UpdateRecord('_grist_Tables', result["id"], {
'primaryViewId': primary_view["id"] if primary_view else 0,
'rawViewSectionRef': raw_section.id if raw_section else 0,
'recordCardViewSectionRef': record_card_section.id if record_card_section else 0,
})
return result
@@ -1924,6 +1976,7 @@ class UserActions(object):
# Copy the columns from the raw view section to a new table.
raw_section = existing_table.rawViewSectionRef
record_card_section = existing_table.recordCardViewSectionRef
raw_section_cols = [f.colRef for f in raw_section.fields]
col_info = [summary.make_col_info(col=c) for c in raw_section_cols]
columns = [summary.get_colinfo_dict(ci, with_id=True) for ci in col_info]
@@ -1933,13 +1986,19 @@ class UserActions(object):
manual_sort=True,
primary_view=False,
raw_section=True,
record_card_section=True,
)
new_table_id = result['table_id']
new_raw_section = self._docmodel.get_table_rec(new_table_id).rawViewSectionRef
new_table = self._docmodel.get_table_rec(new_table_id)
new_raw_section = new_table.rawViewSectionRef
new_record_card_section = new_table.recordCardViewSectionRef
# Copy view section options to the new raw view section.
self._docmodel.update([new_raw_section], options=raw_section.options)
# Copy view section options to the new raw and record card view sections.
self._docmodel.update(
[new_raw_section, new_record_card_section],
options=[raw_section.options, record_card_section.options]
)
old_to_new_col_refs = {}
for existing_field, new_field in zip(raw_section.fields, new_raw_section.fields):