mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Nice summary table IDs
Summary: Changes auto-generated summary table IDs from e.g. `GristSummary_6_Table1` to `Table1_summary_A_B` (meaning `Table1` grouped by `A` and `B`). This makes it easier to write formulas involving summary tables, make API requests, understand logs, etc. Because these don't encode the source table ID as reliably as before, `decode_summary_table_name` now uses the summary table schema info, not just the summary table ID. Specifically, it looks at the type of the `group` column, which is `RefList:<source table id>`. Renaming a source table renames the summary table as before, and now renaming a groupby column renames the summary table as well. Conflicting table names are resolved in the usual way by adding a number at the end, e.g. `Table1_summary_A_B2`. These summary tables are not automatically renamed when the disambiguation is no longer needed. A new migration renames all summary tables to the new scheme, and updates formulas using summary tables with a simple regex. Test Plan: Updated many tests to use the new style of name. Added new Python tests to for resolving conflicts when renaming source tables and groupby columns. Added a test for the migration, including renames in formulas. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3508
This commit is contained in:
@@ -436,7 +436,7 @@ class Engine(object):
|
||||
m = match_counter.MatchCounter(sample)
|
||||
# Iterates through each valid column in the document, counting matches.
|
||||
for c in search_cols:
|
||||
if (not gencode._is_special_table(c.tableId) and
|
||||
if (not (gencode._is_special_table(c.tableId) or c.parentId.summarySourceTable) and
|
||||
column.is_visible_column(c.colId) and
|
||||
not c.type.startswith('Ref')):
|
||||
table = self.tables[c.tableId]
|
||||
@@ -1265,7 +1265,7 @@ class Engine(object):
|
||||
return sum(
|
||||
table._num_rows()
|
||||
for table_id, table in six.iteritems(self.tables)
|
||||
if useractions.is_user_table(table_id)
|
||||
if useractions.is_user_table(table_id) and not table._summary_source_table
|
||||
)
|
||||
|
||||
def apply_user_actions(self, user_actions, user=None):
|
||||
|
||||
@@ -128,7 +128,7 @@ class GenCode(object):
|
||||
If filter_for_user is True, includes only user-visible columns.
|
||||
"""
|
||||
table_id = table_info.tableId
|
||||
source_table_id = summary.decode_summary_table_name(table_id)
|
||||
source_table_id = summary.decode_summary_table_name(table_info)
|
||||
|
||||
# Sort columns by "isFormula" to output all data columns before all formula columns.
|
||||
columns = sorted(six.itervalues(table_info.columns), key=lambda c: c.isFormula)
|
||||
@@ -156,7 +156,7 @@ class GenCode(object):
|
||||
# Collect summary tables to group them by source table.
|
||||
summary_tables = {}
|
||||
for table_info in six.itervalues(schema):
|
||||
source_table_id = summary.decode_summary_table_name(table_info.tableId)
|
||||
source_table_id = summary.decode_summary_table_name(table_info)
|
||||
if source_table_id:
|
||||
summary_tables.setdefault(source_table_id, []).append(table_info)
|
||||
|
||||
@@ -167,7 +167,10 @@ class GenCode(object):
|
||||
for table_info in six.itervalues(schema):
|
||||
fullparts.append("\n\n")
|
||||
fullparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId)))
|
||||
if not _is_special_table(table_info.tableId):
|
||||
if not (
|
||||
_is_special_table(table_info.tableId) or
|
||||
summary.decode_summary_table_name(table_info)
|
||||
):
|
||||
userparts.append("\n\n")
|
||||
userparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId),
|
||||
filter_for_user=True))
|
||||
@@ -193,7 +196,7 @@ class GenCode(object):
|
||||
|
||||
|
||||
def _is_special_table(table_id):
|
||||
return table_id.startswith("_grist_") or bool(summary.decode_summary_table_name(table_id))
|
||||
return table_id.startswith("_grist_")
|
||||
|
||||
|
||||
def exec_module_text(module_text):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
import six
|
||||
from six.moves import xrange
|
||||
@@ -378,7 +379,8 @@ def migration7(tdset):
|
||||
groupby_colrefs = [int(x) for x in m.group(2).strip("_").split("_")]
|
||||
# Prepare a new-style name for the summary table. Be sure not to conflict with existing tables
|
||||
# or with each other (i.e. don't rename multiple tables to the same name).
|
||||
new_name = summary.encode_summary_table_name(source_table_name)
|
||||
groupby_col_ids = [columns_map_by_ref[c].colId for c in groupby_colrefs]
|
||||
new_name = summary.encode_summary_table_name(source_table_name, groupby_col_ids)
|
||||
new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set)
|
||||
table_name_set.add(new_name)
|
||||
log.warn("Upgrading summary table %s for %s(%s) to %s" % (
|
||||
@@ -1014,3 +1016,60 @@ def migration30(tdset):
|
||||
new_view_section_id += 1
|
||||
|
||||
return tdset.apply_doc_actions(doc_actions)
|
||||
|
||||
|
||||
@migration(schema_version=31)
|
||||
def migration31(tdset):
|
||||
columns = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables_column']))
|
||||
tables = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables']))
|
||||
|
||||
tables_by_ref = {t.id: t for t in tables}
|
||||
columns_by_table_ref = defaultdict(list)
|
||||
for col in columns:
|
||||
columns_by_table_ref[col.parentId].append(col)
|
||||
|
||||
table_name_set = {t.tableId for t in tables}
|
||||
|
||||
table_renames = [] # List of (table, new_name) pairs
|
||||
|
||||
for t in six.itervalues(tables_by_ref):
|
||||
if not t.summarySourceTable:
|
||||
continue
|
||||
source_table = tables_by_ref[t.summarySourceTable]
|
||||
# Prepare a new-style name for the summary table. Be sure not to conflict with existing tables
|
||||
# or with each other (i.e. don't rename multiple tables to the same name).
|
||||
groupby_col_ids = [c.colId for c in columns_by_table_ref[t.id] if c.summarySourceCol]
|
||||
new_name = summary.encode_summary_table_name(source_table.tableId, groupby_col_ids)
|
||||
if new_name == t.tableId:
|
||||
continue
|
||||
new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set)
|
||||
table_name_set.add(new_name)
|
||||
log.warn("Upgrading summary table %s for %s(%s) to %s" % (
|
||||
t.tableId, source_table.tableId, groupby_col_ids, new_name))
|
||||
|
||||
# Schedule a rename of the summary table.
|
||||
table_renames.append((t, new_name))
|
||||
|
||||
doc_actions = [
|
||||
actions.RenameTable(t.tableId, new)
|
||||
for (t, new) in table_renames
|
||||
]
|
||||
if table_renames:
|
||||
doc_actions.append(
|
||||
actions.BulkUpdateRecord(
|
||||
'_grist_Tables', [t.id for t, new in table_renames],
|
||||
{'tableId': [new for t, new in table_renames]}
|
||||
)
|
||||
)
|
||||
|
||||
# Update formulas in all columns containing old-style names like 'GristSummary_'
|
||||
for col in columns:
|
||||
if 'GristSummary_' not in col.formula:
|
||||
continue
|
||||
formula = col.formula
|
||||
for table, new_name in table_renames:
|
||||
# Use regex to only match whole words
|
||||
formula = re.sub(r'\b%s\b' % table.tableId, new_name, formula)
|
||||
doc_actions.append(actions.UpdateRecord('_grist_Tables_column', col.id, {'formula': formula}))
|
||||
|
||||
return tdset.apply_doc_actions(doc_actions)
|
||||
|
||||
@@ -15,7 +15,7 @@ import six
|
||||
|
||||
import actions
|
||||
|
||||
SCHEMA_VERSION = 30
|
||||
SCHEMA_VERSION = 31
|
||||
|
||||
def make_column(col_id, col_type, formula='', isFormula=False):
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from collections import namedtuple
|
||||
import json
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
@@ -79,38 +78,34 @@ def _copy_widget_options(options):
|
||||
return options
|
||||
return json.dumps({k: v for k, v in options.items() if k != "rulesOptions"})
|
||||
|
||||
# To generate code, we need to know for each summary table, what its source table is. It would be
|
||||
# easy if we had access to metadata records, but (at least for now) we generate all code based on
|
||||
# schema only. So we encode the source table name inside of the summary table name.
|
||||
#
|
||||
# The encoding includes the length of the source table name, to avoid the possibility of ambiguity
|
||||
# between the second summary table for "Foo", and the first summary table for "Foo2".
|
||||
#
|
||||
# Note that it means we need to rename summary tables when the source table is renamed.
|
||||
|
||||
def encode_summary_table_name(source_table_name):
|
||||
def encode_summary_table_name(source_table_id, groupby_col_ids):
|
||||
"""
|
||||
Create a summary table name that reliably encodes the source table name. It can be decoded even
|
||||
if a suffix is added to the returned name.
|
||||
Create a summary table name based on the source table ID and the groupby column IDs.
|
||||
"""
|
||||
return "GristSummary_%d_%s" % (len(source_table_name), source_table_name)
|
||||
result = source_table_id + '_summary'
|
||||
if groupby_col_ids:
|
||||
result += '_' + '_'.join(sorted(groupby_col_ids))
|
||||
return result
|
||||
|
||||
|
||||
_summary_re = re.compile(r'GristSummary_(\d+)_')
|
||||
|
||||
def decode_summary_table_name(summary_table_name):
|
||||
def decode_summary_table_name(summary_table_info):
|
||||
"""
|
||||
Extract the name of the source table from the summary table name.
|
||||
Extract the name of the source table from the summary table schema info.
|
||||
"""
|
||||
m = _summary_re.match(summary_table_name)
|
||||
if m:
|
||||
start = m.end(0)
|
||||
length = int(m.group(1))
|
||||
source_name = summary_table_name[start : start + length]
|
||||
if len(source_name) == length:
|
||||
return source_name
|
||||
# To generate code, we need to know for each summary table, what its source table is. It would be
|
||||
# easy if we had access to metadata records, but (at least for now) we generate all code based on
|
||||
# schema only. So we use the type of special 'group' column in the summary table.
|
||||
group_col = summary_table_info.columns.get('group')
|
||||
if (
|
||||
group_col
|
||||
and 'getSummarySourceGroup' in group_col.formula
|
||||
and group_col.type.startswith('RefList:')
|
||||
):
|
||||
return group_col.type[8:]
|
||||
return None
|
||||
|
||||
|
||||
def _group_colinfo(source_table):
|
||||
"""Returns ColInfo() for the 'group' column that must be present in every summary table."""
|
||||
return _make_col_info(colId='group', type='RefList:%s' % source_table.tableId,
|
||||
@@ -202,8 +197,9 @@ class SummaryActions(object):
|
||||
summary_table = next((t for t in source_table.summaryTables if t.summaryKey == key), None)
|
||||
created = False
|
||||
if not summary_table:
|
||||
groupby_col_ids = [c.colId for c in groupby_colinfo]
|
||||
result = self.useractions.doAddTable(
|
||||
encode_summary_table_name(source_table.tableId),
|
||||
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)
|
||||
|
||||
@@ -208,7 +208,7 @@ class TestColumnActions(test_engine.EngineTestCase):
|
||||
Column(16, "B", "Text", False, "", 0),
|
||||
Column(17, "C", "Any", True, "", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(18, "state", "Text", False, "", summarySourceCol=12),
|
||||
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
|
||||
formula="table.getSummarySourceGroup(rec)"),
|
||||
@@ -249,7 +249,7 @@ class TestColumnActions(test_engine.EngineTestCase):
|
||||
[ 2, "b", "e", None, 2.0],
|
||||
[ 3, "c", "f", None, 3.0],
|
||||
])
|
||||
self.assertTableData("GristSummary_7_Address", cols="subset", data=[
|
||||
self.assertTableData("Address_summary_state", cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
@@ -297,7 +297,7 @@ class TestColumnActions(test_engine.EngineTestCase):
|
||||
Column(15, "A", "Text", False, "", 0),
|
||||
Column(17, "C", "Any", True, "", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(18, "state", "Text", False, "", summarySourceCol=12),
|
||||
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
|
||||
formula="table.getSummarySourceGroup(rec)"),
|
||||
@@ -356,7 +356,7 @@ class TestColumnActions(test_engine.EngineTestCase):
|
||||
Column(17, "C", "Any", True, "", 0),
|
||||
]),
|
||||
# Note that the summary table here switches to a new one, without the deleted group-by.
|
||||
Table(4, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(4, "Address_summary", 0, 1, columns=[
|
||||
Column(23, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
|
||||
Column(24, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
|
||||
Column(22, "group", "RefList:Address", True, summarySourceCol=0,
|
||||
@@ -406,7 +406,7 @@ class TestColumnActions(test_engine.EngineTestCase):
|
||||
[ 2, "b", None, 2.0],
|
||||
[ 3, "c", None, 3.0],
|
||||
])
|
||||
self.assertTableData("GristSummary_7_Address2", cols="subset", data=[
|
||||
self.assertTableData("Address_summary", cols="subset", data=[
|
||||
[ "id", "count", "amount" ],
|
||||
[ 1, 7+1+1+2, 1.+2+6+7+8+10+11+3+4+5+9 ],
|
||||
])
|
||||
|
||||
@@ -60,7 +60,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None])
|
||||
|
||||
# Check the results.
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[1, 2012, 1, 15, [1]],
|
||||
[2, 2013, 2, 30, [2,3]],
|
||||
[3, 2014, 3, 86, [4,5,6]],
|
||||
@@ -75,9 +75,9 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.BulkUpdateRecord("Orders", [1,2], {'amount': [14, 14]}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,2], {'amount': [14, 29]})
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,2], {'amount': [14, 29]})
|
||||
],
|
||||
"calls": {"GristSummary_6_Orders": {"amount": 2}}
|
||||
"calls": {"Orders_summary_year": {"amount": 2}}
|
||||
})
|
||||
|
||||
# Changing a record from one product to another should cause the two affected lines to change.
|
||||
@@ -85,16 +85,16 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.UpdateRecord("Orders", 10, {"year": 2012}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"amount": [31.0, 89.0]}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"count": [2,3]}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"group": [[1,10], [7,8,9]]}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"amount": [31.0, 89.0]}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"count": [2,3]}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"group": [[1,10], [7,8,9]]}),
|
||||
],
|
||||
"calls": {"GristSummary_6_Orders": {"group": 2, "amount": 2, "count": 2},
|
||||
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
|
||||
"#summary#GristSummary_6_Orders": 1}}
|
||||
"calls": {"Orders_summary_year": {"group": 2, "amount": 2, "count": 2},
|
||||
"Orders": {"#lookup##summary#Orders_summary_year": 1,
|
||||
"#summary#Orders_summary_year": 1}}
|
||||
})
|
||||
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[1, 2012, 2, 31.0, [1,10]],
|
||||
[2, 2013, 2, 29.0, [2,3]],
|
||||
[3, 2014, 3, 86.0, [4,5,6]],
|
||||
@@ -106,20 +106,20 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.UpdateRecord("Orders", 10, {"year": 1999}),
|
||||
actions.AddRecord("GristSummary_6_Orders", 5, {'year': 1999}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"amount": [14.0, 17.0]}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"count": [1,1]}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"group": [[1], [10]]}),
|
||||
actions.AddRecord("Orders_summary_year", 5, {'year': 1999}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"amount": [14.0, 17.0]}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"count": [1,1]}),
|
||||
actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"group": [[1], [10]]}),
|
||||
],
|
||||
"calls": {
|
||||
"GristSummary_6_Orders": {
|
||||
"Orders_summary_year": {
|
||||
'#lookup#year': 1, "group": 2, "amount": 2, "count": 2, "#lookup#": 1
|
||||
},
|
||||
"Orders": {"#lookup##summary#GristSummary_6_Orders": 1,
|
||||
"#summary#GristSummary_6_Orders": 1}}
|
||||
"Orders": {"#lookup##summary#Orders_summary_year": 1,
|
||||
"#summary#Orders_summary_year": 1}}
|
||||
})
|
||||
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[1, 2012, 1, 14.0, [1]],
|
||||
[2, 2013, 2, 29.0, [2,3]],
|
||||
[3, 2014, 3, 86.0, [4,5,6]],
|
||||
@@ -135,7 +135,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.load_sample(self.sample)
|
||||
|
||||
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10, 12], None])
|
||||
self.assertPartialData("GristSummary_6_Orders", [
|
||||
self.assertPartialData("Orders_summary_product_year", [
|
||||
"id", "year", "product", "count", "amount", "group"
|
||||
], [
|
||||
[1, 2012, "A", 1, 15.0, [1]],
|
||||
@@ -156,23 +156,23 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.BulkUpdateRecord("Orders", [2, 6, 7], {"product": ["B", "B", "C"]}),
|
||||
actions.AddRecord("GristSummary_6_Orders", 7, {'year': 2013, 'product': 'B'}),
|
||||
actions.AddRecord("GristSummary_6_Orders", 8, {'year': 2015, 'product': 'C'}),
|
||||
actions.RemoveRecord("GristSummary_6_Orders", 4),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
|
||||
actions.AddRecord("Orders_summary_product_year", 7, {'year': 2013, 'product': 'B'}),
|
||||
actions.AddRecord("Orders_summary_product_year", 8, {'year': 2015, 'product': 'C'}),
|
||||
actions.RemoveRecord("Orders_summary_product_year", 4),
|
||||
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
|
||||
"amount": [15.0, 86.0, 17.0, 15.0, 17.0]
|
||||
}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
|
||||
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
|
||||
"count": [1, 3, 1, 1, 1]
|
||||
}),
|
||||
actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], {
|
||||
actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], {
|
||||
"group": [[3], [4,5,6], [10], [2], [7]]
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# Verify the results.
|
||||
self.assertPartialData("GristSummary_6_Orders", [
|
||||
self.assertPartialData("Orders_summary_product_year", [
|
||||
"id", "year", "product", "count", "amount", "group"
|
||||
], [
|
||||
[1, 2012, "A", 1, 15.0, [1]],
|
||||
@@ -194,10 +194,10 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
|
||||
# Create a summary on the Customers table. Adding orders involves a lookup for each customer.
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, 'record', [3], None])
|
||||
self.add_column("GristSummary_9_Customers", "totalAmount",
|
||||
self.add_column("Customers_summary_state", "totalAmount",
|
||||
formula="sum(sum(Orders.lookupRecords(customer=c).amount) for c in $group)")
|
||||
|
||||
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
|
||||
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
|
||||
[1, "NY", 2, 103.0 ],
|
||||
[2, "CT", 2, 134.0 ],
|
||||
[3, "NJ", 1, 0.0 ],
|
||||
@@ -219,14 +219,14 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.UpdateRecord("Orders", 9, {"amount": 37}),
|
||||
actions.UpdateRecord("GristSummary_9_Customers", 2, {"totalAmount": 135.0}),
|
||||
actions.UpdateRecord("Customers_summary_state", 2, {"totalAmount": 135.0}),
|
||||
]
|
||||
})
|
||||
|
||||
# In either case, changing a customer's state should trigger recomputation too.
|
||||
# We are changing a NY customer with $51 in orders to MA.
|
||||
self.update_record('Customers', 2, state="MA")
|
||||
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
|
||||
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
|
||||
[1, "NY", 1, 52.0 ],
|
||||
[2, "CT", 2, 135.0 ],
|
||||
[3, "NJ", 1, 0.0 ],
|
||||
@@ -246,7 +246,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
# actions.AddRecord("Summary4", 4, {"state": "NJ"}),
|
||||
# actions.UpdateRecord("Summary4", 4, {"manualSort": 4.0})]
|
||||
# })
|
||||
self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [
|
||||
self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [
|
||||
[1, "NY", 1, 35.0 ],
|
||||
[2, "CT", 2, 135.0 ],
|
||||
[3, "NJ", 1, 17.0 ],
|
||||
@@ -264,7 +264,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
|
||||
# Create a summary table summarizing count and total of orders by year.
|
||||
self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None])
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[1, 2012, 1, 15.0, [1]],
|
||||
[2, 2013, 2, 30.0, [2,3]],
|
||||
[3, 2014, 3, 86.0, [4,5,6]],
|
||||
@@ -273,7 +273,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
|
||||
# Update a record so that a new line appears in the summary table.
|
||||
out_actions_update = self.update_record("Orders", 1, year=2007)
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[2, 2013, 2, 30.0, [2,3]],
|
||||
[3, 2014, 3, 86.0, [4,5,6]],
|
||||
[4, 2015, 4, 106.0, [7,8,9,10]],
|
||||
@@ -282,7 +282,7 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
|
||||
# Undo and ensure that the new line is gone from the summary table.
|
||||
out_actions_undo = self.apply_undo_actions(out_actions_update.undo)
|
||||
self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [
|
||||
self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [
|
||||
[1, 2012, 1, 15.0, [1]],
|
||||
[2, 2013, 2, 30.0, [2,3]],
|
||||
[3, 2014, 3, 86.0, [4,5,6]],
|
||||
@@ -290,18 +290,18 @@ class TestDerived(test_engine.EngineTestCase):
|
||||
])
|
||||
self.assertPartialOutActions(out_actions_undo, {
|
||||
"stored": [
|
||||
actions.AddRecord("GristSummary_6_Orders", 1, {
|
||||
actions.AddRecord("Orders_summary_year", 1, {
|
||||
"amount": 15.0, "count": 1, "group": [1], "year": 2012
|
||||
}),
|
||||
actions.RemoveRecord("GristSummary_6_Orders", 5),
|
||||
actions.RemoveRecord("Orders_summary_year", 5),
|
||||
actions.UpdateRecord("Orders", 1, {"year": 2012}),
|
||||
],
|
||||
"calls": {
|
||||
"GristSummary_6_Orders": {
|
||||
"Orders_summary_year": {
|
||||
"#lookup#": 1, "#lookup#year": 1, "group": 1, "amount": 1, "count": 1
|
||||
},
|
||||
"Orders": {
|
||||
"#lookup##summary#GristSummary_6_Orders": 1, "#summary#GristSummary_6_Orders": 1,
|
||||
"#lookup##summary#Orders_summary_year": 1, "#summary#Orders_summary_year": 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -66,25 +66,10 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
def test_encode_summary_table_name(self):
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo"), "GristSummary_3_Foo")
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo2"), "GristSummary_4_Foo2")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo"), "Foo")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2"), "Foo2")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo2"), "Foo")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2_2"), "Foo2")
|
||||
# Test that underscore in the name is OK.
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_5_Foo_234"), "Foo_2")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo_234"), "Foo_")
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_6__Foo_234"), "_Foo_2")
|
||||
# Test that we return None for invalid values.
|
||||
self.assertEqual(summary.decode_summary_table_name("Foo2"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_3Foo"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary_3X_Foo"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("_5_Foo_234"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("_GristSummary_3_Foo"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("gristsummary_3_Foo"), None)
|
||||
self.assertEqual(summary.decode_summary_table_name("GristSummary3_Foo"), None)
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo", []), "Foo_summary")
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo", ["A"]), "Foo_summary_A")
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo", ["A", "B"]), "Foo_summary_A_B")
|
||||
self.assertEqual(summary.encode_summary_table_name("Foo", ["B", "A"]), "Foo_summary_A_B")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
@@ -116,7 +101,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
|
||||
# Verify that a new table gets created, and a new view, with a section for that table,
|
||||
# and some auto-generated summary fields.
|
||||
summary_table1 = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1,
|
||||
summary_table1 = Table(2, "Address_summary", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(14, "group", "RefList:Address", isFormula=True, summarySourceCol=0,
|
||||
formula="table.getSummarySourceGroup(rec)"),
|
||||
@@ -135,7 +120,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
self.assertViews([basic_view, summary_view1])
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary', cols="subset", data=[
|
||||
[ "id", "count", "amount"],
|
||||
[ 1, 11, 66.0 ],
|
||||
])
|
||||
@@ -145,7 +130,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
|
||||
# Verify that a new table gets created again, a new view, and a section for that table.
|
||||
# Note that we also check that summarySourceTable and summarySourceCol fields are correct.
|
||||
summary_table2 = Table(3, "GristSummary_7_Address2", primaryViewId=0, summarySourceTable=1,
|
||||
summary_table2 = Table(3, "Address_summary_state", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(17, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
|
||||
Column(18, "group", "RefList:Address", isFormula=True, summarySourceCol=0,
|
||||
@@ -173,7 +158,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
])
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
@@ -185,7 +170,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
|
||||
|
||||
# Verify the new table and views.
|
||||
summary_table3 = Table(4, "GristSummary_7_Address3", primaryViewId=0, summarySourceTable=1,
|
||||
summary_table3 = Table(4, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(21, "city", "Text", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(22, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
|
||||
@@ -208,7 +193,7 @@ class TestSummary(test_engine.EngineTestCase):
|
||||
self.assertViews([basic_view, summary_view1, summary_view2, summary_view3])
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="subset", data=[
|
||||
[ "id", "city", "state", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , 3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , 1, 2. ],
|
||||
@@ -269,7 +254,7 @@ class Address:
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
|
||||
|
||||
# Verify the new table and views.
|
||||
summary_table = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1,
|
||||
summary_table = Table(2, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(14, "city", "Text", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(15, "state", "Text", isFormula=False, formula="", summarySourceCol=12),
|
||||
@@ -316,7 +301,7 @@ class Address:
|
||||
self.assertViews([summary_view, summary_view2, summary_view3])
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="subset", data=[
|
||||
[ "id", "city", "state", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , 3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , 1, 2. ],
|
||||
@@ -342,12 +327,12 @@ class Address:
|
||||
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary", 0, 1, columns=[
|
||||
Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(15, "count", "Int", True, "len($group)", 0),
|
||||
Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(17, "state", "Text", False, "", 12),
|
||||
Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(19, "count", "Int", True, "len($group)", 0),
|
||||
@@ -370,8 +355,8 @@ class Address:
|
||||
self.assertTableData("_grist_Tables", cols="subset", data=[
|
||||
['id', 'tableId', 'summarySourceTable'],
|
||||
[ 1, 'Address', 0],
|
||||
[ 2, 'GristSummary_7_Address', 1],
|
||||
[ 3, 'GristSummary_7_Address2', 1],
|
||||
[ 2, 'Address_summary', 1],
|
||||
[ 3, 'Address_summary_state', 1],
|
||||
[ 4, 'Address2', 0],
|
||||
])
|
||||
|
||||
@@ -382,12 +367,12 @@ class Address:
|
||||
# Make sure this creates new section rather than reuses similar ones for the wrong table.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary", 0, 1, columns=[
|
||||
Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(15, "count", "Int", True, "len($group)", 0),
|
||||
Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(17, "state", "Text", False, "", 12),
|
||||
Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(19, "count", "Int", True, "len($group)", 0),
|
||||
@@ -399,12 +384,12 @@ class Address:
|
||||
Column(23, "state", "Text", False, "", 0),
|
||||
Column(24, "amount", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(5, "GristSummary_8_Address2", 0, 4, columns=[
|
||||
Table(5, "Address2_summary", 0, 4, columns=[
|
||||
Column(25, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(26, "count", "Int", True, "len($group)", 0),
|
||||
Column(27, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(6, "GristSummary_8_Address2_2", 0, 4, columns=[
|
||||
Table(6, "Address2_summary_state", 0, 4, columns=[
|
||||
Column(28, "state", "Text", False, "", 23),
|
||||
Column(29, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(30, "count", "Int", True, "len($group)", 0),
|
||||
@@ -424,7 +409,7 @@ class Address:
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
|
||||
|
||||
# Verify that the summary table respects all updates to the source table.
|
||||
self._do_test_updates("Address", "GristSummary_7_Address")
|
||||
self._do_test_updates("Address", "Address_summary_city_state")
|
||||
|
||||
def _do_test_updates(self, source_tbl_name, summary_tbl_name):
|
||||
# This is the main part of test_summary_updates(). It's moved to its own method so that
|
||||
@@ -541,7 +526,7 @@ class Address:
|
||||
# Check what tables we have now.
|
||||
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
|
||||
[1, "Address", 0],
|
||||
[2, "GristSummary_7_Address", 1],
|
||||
[2, "Address_summary_city_state", 1],
|
||||
])
|
||||
|
||||
# Rename the table: this is what we are really testing in this test case.
|
||||
@@ -549,11 +534,11 @@ class Address:
|
||||
|
||||
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
|
||||
[1, "Location", 0],
|
||||
[2, "GristSummary_8_Location", 1],
|
||||
[2, "Location_summary_city_state", 1],
|
||||
])
|
||||
|
||||
# Verify that the bigger summary table respects all updates to the renamed source table.
|
||||
self._do_test_updates("Location", "GristSummary_8_Location")
|
||||
self._do_test_updates("Location", "Location_summary_city_state")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
@@ -565,11 +550,11 @@ class Address:
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
|
||||
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
|
||||
[1, "Address", 0],
|
||||
[2, "GristSummary_7_Address", 1],
|
||||
[3, "GristSummary_7_Address2", 1],
|
||||
[2, "Address_summary_city_state", 1],
|
||||
[3, "Address_summary", 1],
|
||||
])
|
||||
# Verify the data in the simple totals-only summary table.
|
||||
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
|
||||
self.assertTableData('Address_summary', cols="subset", data=[
|
||||
[ "id", "count", "amount"],
|
||||
[ 1, 11, 66.0 ],
|
||||
])
|
||||
@@ -578,21 +563,21 @@ class Address:
|
||||
self.apply_user_action(["RenameTable", "Address", "Addresses"])
|
||||
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
|
||||
[1, "Addresses", 0],
|
||||
[2, "GristSummary_9_Addresses", 1],
|
||||
[3, "GristSummary_9_Addresses2", 1],
|
||||
[2, "Addresses_summary_city_state", 1],
|
||||
[3, "Addresses_summary", 1],
|
||||
])
|
||||
self.assertTableData('GristSummary_9_Addresses2', cols="subset", data=[
|
||||
self.assertTableData('Addresses_summary', cols="subset", data=[
|
||||
[ "id", "count", "amount"],
|
||||
[ 1, 11, 66.0 ],
|
||||
])
|
||||
|
||||
# Remove one of the tables so that we can use _do_test_updates to verify updates still work.
|
||||
self.apply_user_action(["RemoveTable", "GristSummary_9_Addresses2"])
|
||||
self.apply_user_action(["RemoveTable", "Addresses_summary"])
|
||||
self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [
|
||||
[1, "Addresses", 0],
|
||||
[2, "GristSummary_9_Addresses", 1],
|
||||
[2, "Addresses_summary_city_state", 1],
|
||||
])
|
||||
self._do_test_updates("Addresses", "GristSummary_9_Addresses")
|
||||
self._do_test_updates("Addresses", "Addresses_summary_city_state")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
@@ -610,14 +595,14 @@ class Address:
|
||||
# These are the tables and columns we automatically get.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "count", "Int", True, "len($group)", 0),
|
||||
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -626,7 +611,7 @@ class Address:
|
||||
|
||||
# Now change a formula using one of the summary tables. It should trigger an equivalent
|
||||
# change in the other.
|
||||
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount",
|
||||
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount",
|
||||
{"formula": "10*sum($group.amount)"}])
|
||||
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
|
||||
['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'],
|
||||
@@ -635,7 +620,7 @@ class Address:
|
||||
])
|
||||
|
||||
# Change a formula and a few other fields in the other table, and verify a change to both.
|
||||
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address2", "amount",
|
||||
self.apply_user_action(["ModifyColumn", "Address_summary", "amount",
|
||||
{"formula": "100*sum($group.amount)",
|
||||
"type": "Text",
|
||||
"widgetOptions": "hello",
|
||||
@@ -649,7 +634,7 @@ class Address:
|
||||
])
|
||||
|
||||
# Check the values in the summary tables: they should reflect the new formula.
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="subset", data=[
|
||||
[ "id", "city", "state", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , 3, str(100*(1+6+11))],
|
||||
[ 2, "Albany", "NY" , 1, "200" ],
|
||||
@@ -661,7 +646,7 @@ class Address:
|
||||
[ 8, "Boston", "MA" , 1, "900" ],
|
||||
[ 9, "Yonkers", "NY" , 1, "1000" ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
|
||||
self.assertTableData('Address_summary', cols="subset", data=[
|
||||
[ "id", "count", "amount"],
|
||||
[ 1, 11, "6600"],
|
||||
])
|
||||
@@ -670,19 +655,19 @@ class Address:
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "count", "Int", True, "len($group)", 0),
|
||||
Column(18, "amount", "Text", True, "100*sum($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Text", True, "100*sum($group.amount)", 0),
|
||||
]),
|
||||
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
|
||||
Table(4, "Address_summary_state", 0, 1, columns=[
|
||||
Column(22, "state", "Text", False, "", 12),
|
||||
Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(24, "count", "Int", True, "len($group)", 0),
|
||||
@@ -697,7 +682,7 @@ class Address:
|
||||
])
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, str(int(100*(1.+2+6+7+8+10+11))) ],
|
||||
[ 2, "WA", 1, "300" ],
|
||||
@@ -722,7 +707,7 @@ class Address:
|
||||
Column(3, "B", "Numeric", False, "", 0),
|
||||
Column(4, "C", "Any", True, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Column(5, "A", "Numeric", False, "", 2),
|
||||
Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(7, "count", "Int", True, "len($group)", 0),
|
||||
@@ -735,7 +720,7 @@ class Address:
|
||||
[ 2, 2.0, 20, 2.0, None ],
|
||||
[ 3, 3.0, 10, 3.0, None ],
|
||||
])
|
||||
self.assertTableData('GristSummary_6_Table1', data=[
|
||||
self.assertTableData('Table1_summary_A', data=[
|
||||
[ "id", "A", "group", "count", "B" ],
|
||||
[ 1, 10, [1,3], 2, 4 ],
|
||||
[ 2, 20, [2], 1, 2 ],
|
||||
@@ -753,7 +738,7 @@ class Address:
|
||||
Column(3, "B", "Numeric", False, "", 0),
|
||||
Column(4, "C", "Any", True, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Column(5, "A", "Text", False, "", 2),
|
||||
Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(7, "count", "Int", True, "len($group)", 0),
|
||||
@@ -766,7 +751,7 @@ class Address:
|
||||
[ 2, 2.0, "20", 2.0, None ],
|
||||
[ 3, 3.0, "10", 3.0, None ],
|
||||
])
|
||||
self.assertTableData('GristSummary_6_Table1', data=[
|
||||
self.assertTableData('Table1_summary_A', data=[
|
||||
[ "id", "A", "group", "count", "B" ],
|
||||
[ 1, "10", [1,3], 2, 4 ],
|
||||
[ 2, "20", [2], 1, 2 ],
|
||||
@@ -791,7 +776,7 @@ class Address:
|
||||
Column(3, "B", "Numeric", False, "", 0),
|
||||
Column(4, "C", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Table(2, "Table1_summary_A_B", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Column(5, "A", "Text", False, "", 2),
|
||||
Column(6, "B", "Numeric", False, "", 3),
|
||||
Column(7, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -805,7 +790,7 @@ class Address:
|
||||
[ 2, 2.0, 'b', 1.0, 5 ],
|
||||
[ 3, 3.0, 'c', 2.0, 6 ],
|
||||
])
|
||||
self.assertTableData('GristSummary_6_Table1', data=[
|
||||
self.assertTableData('Table1_summary_A_B', data=[
|
||||
[ "id", "A", "B", "group", "count", "C" ],
|
||||
[ 1, 'a', 1.0, [1], 1, 4 ],
|
||||
[ 2, 'b', 1.0, [2], 1, 5 ],
|
||||
@@ -822,7 +807,7 @@ class Address:
|
||||
Column(3, "B", "Numeric", False, "", 0),
|
||||
Column(4, "C", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_6_Table1_2", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Table(3, "Table1_summary_B", summarySourceTable=1, primaryViewId=0, columns=[
|
||||
Column(10, "B", "Numeric", False, "", 3),
|
||||
Column(12, "count", "Int", True, "len($group)", 0),
|
||||
Column(13, "C", "Numeric", True, "SUM($group.C)", 0),
|
||||
@@ -835,7 +820,7 @@ class Address:
|
||||
[ 2, 2.0, 1.0, 5 ],
|
||||
[ 3, 3.0, 2.0, 6 ],
|
||||
])
|
||||
self.assertTableData('GristSummary_6_Table1_2', data=[
|
||||
self.assertTableData('Table1_summary_B', data=[
|
||||
[ "id", "B", "group", "count", "C" ],
|
||||
[ 1, 1.0, [1,2], 2, 9 ],
|
||||
[ 2, 2.0, [3], 1, 6 ],
|
||||
|
||||
@@ -4,9 +4,9 @@ files: test_summary.py and test_summary2.py.
|
||||
"""
|
||||
import actions
|
||||
import logger
|
||||
import objtypes
|
||||
import test_engine
|
||||
import test_summary
|
||||
import testutil
|
||||
|
||||
from test_engine import Table, Column, View, Section, Field
|
||||
|
||||
@@ -32,15 +32,15 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
|
||||
# Check that we cannot add a non-formula column.
|
||||
with self.assertRaisesRegex(ValueError, r'non-formula column'):
|
||||
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "average",
|
||||
self.apply_user_action(["AddColumn", "Address_summary_city_state", "average",
|
||||
{"type": "Text", "isFormula": False}])
|
||||
|
||||
# Add two formula columns: one for 'state' (an existing column name, and a group-by column in
|
||||
# some tables), and one for 'average' (a new column name).
|
||||
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address2", "state",
|
||||
self.apply_user_action(["AddVisibleColumn", "Address_summary", "state",
|
||||
{"formula": "':'.join(sorted(set($group.state)))"}])
|
||||
|
||||
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average",
|
||||
self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average",
|
||||
{"formula": "$amount / $count"}])
|
||||
|
||||
# Add two more summary tables: by 'city', and by 'state', and see what columns they get.
|
||||
@@ -52,7 +52,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# Check the table and columns for all the summary tables.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -60,13 +60,13 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
Column(23, "average", "Any", True, "$amount / $count", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
Column(22, "state", "Any", True, "':'.join(sorted(set($group.state)))", 0),
|
||||
]),
|
||||
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
|
||||
Table(4, "Address_summary_city", 0, 1, columns=[
|
||||
Column(24, "city", "Text", False, "", 11),
|
||||
Column(25, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(26, "count", "Int", True, "len($group)", 0),
|
||||
@@ -74,7 +74,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(28, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
# Note that since 'state' is used as a group-by column here, we skip the 'state' formula.
|
||||
Table(5, "GristSummary_7_Address4", 0, 1, columns=[
|
||||
Table(5, "Address_summary_state", 0, 1, columns=[
|
||||
Column(29, "state", "Text", False, "", 12),
|
||||
Column(30, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(31, "count", "Int", True, "len($group)", 0),
|
||||
@@ -106,7 +106,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
|
||||
|
||||
# Check that the data is as we expect.
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="all", data=[
|
||||
[ "id", "city", "state", "group", "count", "amount", "average" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , (1.+6+11)/3 ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. , 2. ],
|
||||
@@ -118,11 +118,11 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, "Boston", "MA" , [29], 1, 9. , 9. ],
|
||||
[ 9, "Yonkers", "NY" , [30], 1, 10. , 10. ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
|
||||
self.assertTableData('Address_summary', cols="all", data=[
|
||||
[ "id", "count", "amount", "state" , "group" ],
|
||||
[ 1, 11, 66.0 , "IL:MA:NY:WA" , [21,22,23,24,25,26,27,28,29,30,31]],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address3', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city', cols="subset", data=[
|
||||
[ "id", "city", "count", "amount", "state" ],
|
||||
[ 1, "New York", 3, 1.+6+11 , "NY" ],
|
||||
[ 2, "Albany", 1, 2. , "NY" ],
|
||||
@@ -133,7 +133,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 7, "Boston", 1, 9. , "MA" ],
|
||||
[ 8, "Yonkers", 1, 10. , "NY" ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address4', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
@@ -146,16 +146,16 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
actions.UpdateRecord("Address", 28, {'state': 'MA'}),
|
||||
actions.RemoveRecord("GristSummary_7_Address", 7),
|
||||
actions.UpdateRecord("GristSummary_7_Address", 5, {'amount': 5.0 + 8.0}),
|
||||
actions.UpdateRecord("GristSummary_7_Address", 5, {'average': 6.5}),
|
||||
actions.UpdateRecord("GristSummary_7_Address", 5, {'count': 2}),
|
||||
actions.UpdateRecord("GristSummary_7_Address", 5, {'group': [25, 28]}),
|
||||
actions.UpdateRecord("GristSummary_7_Address3", 5, {'state': "MA"}),
|
||||
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4],
|
||||
actions.RemoveRecord("Address_summary_city_state", 7),
|
||||
actions.UpdateRecord("Address_summary_city", 5, {'state': "MA"}),
|
||||
actions.UpdateRecord("Address_summary_city_state", 5, {'amount': 5.0 + 8.0}),
|
||||
actions.UpdateRecord("Address_summary_city_state", 5, {'average': 6.5}),
|
||||
actions.UpdateRecord("Address_summary_city_state", 5, {'count': 2}),
|
||||
actions.UpdateRecord("Address_summary_city_state", 5, {'group': [25, 28]}),
|
||||
actions.BulkUpdateRecord("Address_summary_state", [1,4],
|
||||
{'amount': [1.+2+6+7+10+11, 5.+8+9]}),
|
||||
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4], {'count': [6, 3]}),
|
||||
actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4],
|
||||
actions.BulkUpdateRecord("Address_summary_state", [1,4], {'count': [6, 3]}),
|
||||
actions.BulkUpdateRecord("Address_summary_state", [1,4],
|
||||
{'group': [[21,22,26,27,30,31], [25,28,29]]}),
|
||||
]
|
||||
})
|
||||
@@ -175,11 +175,11 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# Check that we cannot rename a summary group-by column. (Perhaps it's better to raise an
|
||||
# exception, but currently we translate the invalid request to a no-op.)
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'):
|
||||
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "s"])
|
||||
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "s"])
|
||||
|
||||
# Verify all data. We'll repeat this after renamings to make sure there are no errors.
|
||||
self.assertTableData("Address", self.starting_table_data)
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="all", data=[
|
||||
[ "id", "city", "state", "group", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. ],
|
||||
@@ -191,25 +191,25 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, "Boston", "MA" , [29], 1, 9. ],
|
||||
[ 9, "Yonkers", "NY" , [30], 1, 10. ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
|
||||
self.assertTableData('Address_summary', cols="all", data=[
|
||||
[ "id", "count", "amount", "group" ],
|
||||
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
|
||||
])
|
||||
|
||||
# This should work fine, and should affect sister tables.
|
||||
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "count", "xcount"])
|
||||
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "count", "xcount"])
|
||||
|
||||
# These are the tables and columns we automatically get.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "xcount", "Int", True, "len($group)", 0),
|
||||
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "xcount", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -226,14 +226,14 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(12, "xstate", "Text", False, "", 0),
|
||||
Column(13, "xamount", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_xstate", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "xstate", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "xcount", "Int", True, "len($group)", 0),
|
||||
Column(18, "xamount", "Numeric", True, "SUM($group.xamount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "xcount", "Int", True, "len($group)", 0),
|
||||
Column(21, "xamount", "Numeric", True, "SUM($group.xamount)", 0),
|
||||
@@ -247,7 +247,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
address_table_data = replace_col_names(
|
||||
self.starting_table_data, state='xstate', amount='xamount')
|
||||
self.assertTableData("Address", address_table_data)
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_xstate', cols="all", data=[
|
||||
[ "id", "city", "xstate", "group", "xcount", "xamount" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. ],
|
||||
@@ -259,14 +259,14 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, "Boston", "MA" , [29], 1, 9. ],
|
||||
[ 9, "Yonkers", "NY" , [30], 1, 10. ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
|
||||
self.assertTableData('Address_summary', cols="all", data=[
|
||||
[ "id", "xcount", "xamount", "group" ],
|
||||
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
|
||||
])
|
||||
|
||||
|
||||
# Add a conflicting name to a summary table and see how renames behave.
|
||||
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo",
|
||||
self.apply_user_action(["AddColumn", "Address_summary_city_xstate", "foo",
|
||||
{"formula": "$xamount * 100"}])
|
||||
self.apply_user_action(["RenameColumn", "Address", "xstate", "foo"])
|
||||
self.apply_user_action(["RenameColumn", "Address", "xamount", "foo"])
|
||||
@@ -278,7 +278,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(12, "foo2", "Text", False, "", 0),
|
||||
Column(13, "foo3", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_foo2", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "foo2", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -286,7 +286,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(18, "foo3", "Numeric", True, "SUM($group.foo3)", 0),
|
||||
Column(22, "foo", "Any", True, "$foo3 * 100", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "xcount", "Int", True, "len($group)", 0),
|
||||
Column(21, "foo3", "Numeric", True, "SUM($group.foo3)", 0),
|
||||
@@ -297,7 +297,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
address_table_data = replace_col_names(
|
||||
address_table_data, xstate='foo2', xamount='foo3')
|
||||
self.assertTableData("Address", address_table_data)
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_foo2', cols="all", data=[
|
||||
[ "id", "city", "foo2" , "group", "xcount", "foo3", "foo" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11, 100*(1.+6+11) ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. , 100*(2.) ],
|
||||
@@ -309,7 +309,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, "Boston", "MA" , [29], 1, 9. , 100*(9.) ],
|
||||
[ 9, "Yonkers", "NY" , [30], 1, 10. , 100*(10.) ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
|
||||
self.assertTableData('Address_summary', cols="all", data=[
|
||||
[ "id", "xcount", "foo3" , "group" ],
|
||||
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
|
||||
])
|
||||
@@ -328,6 +328,145 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[21, 'foo3', True, 'WidgetOptions2'],
|
||||
], rows=lambda r: r.colId in ('foo2', 'foo3'))
|
||||
|
||||
def test_summary_col_rename_conflict(self):
|
||||
sample = testutil.parse_test_sample({
|
||||
"SCHEMA": [
|
||||
[1, "Table1", [
|
||||
[11, "A", "Text", False, "", "A", ""],
|
||||
[12, "B", "Text", False, "", "B", ""],
|
||||
]],
|
||||
[2, "Table1_summary_A_B", [
|
||||
[13, "A", "Text", False, "", "A", ""],
|
||||
]],
|
||||
],
|
||||
"DATA": {}
|
||||
})
|
||||
self.load_sample(sample)
|
||||
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
|
||||
|
||||
table1 = Table(
|
||||
1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[
|
||||
Column(11, "A", "Text", False, "", 0),
|
||||
Column(12, "B", "Text", False, "", 0),
|
||||
],
|
||||
)
|
||||
|
||||
# Normal table whose name conflicts with the automatically-generated summary table name below
|
||||
fake_summary = Table(
|
||||
2, "Table1_summary_A_B", primaryViewId=0, summarySourceTable=0, columns=[
|
||||
Column(13, "A", "Text", False, "", 0),
|
||||
],
|
||||
)
|
||||
|
||||
# Auto-generated name has to have a '2' to disambiguate from the normal table.
|
||||
summary_by_a_and_b = Table(
|
||||
3, "Table1_summary_A_B2", primaryViewId=0, summarySourceTable=1, columns=[
|
||||
Column(14, "A", "Text", False, "", 11),
|
||||
Column(15, "B", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "count", "Int", True, "len($group)", 0),
|
||||
],
|
||||
)
|
||||
|
||||
# nothing special here yet
|
||||
summary_by_a = Table(
|
||||
4, "Table1_summary_A", primaryViewId=0, summarySourceTable=1, columns=[
|
||||
Column(18, "A", "Text", False, "", 11),
|
||||
Column(19, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
],
|
||||
)
|
||||
|
||||
tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a]
|
||||
self.assertTables(tables)
|
||||
|
||||
# Add some formulas using summary table names that are about to change
|
||||
self.add_column("Table1", "summary_ref1",
|
||||
type="RefList:Table1_summary_A_B2",
|
||||
formula="Table1_summary_A_B2.lookupRecords(A=1)",
|
||||
isFormula=True)
|
||||
self.add_column("Table1", "summary_ref2",
|
||||
type="Ref:Table1_summary_A",
|
||||
formula="Table1_summary_A.lookupOne(A=23)",
|
||||
isFormula=True)
|
||||
|
||||
# I got the weirdest heisenbug ever when renaming straight from A to A_B.
|
||||
# The order of renaming is not deterministic so it may end up with
|
||||
# 'Table1_summary_A_B3', but asserting that name made it come out as
|
||||
# 'Table1_summary_A_B2' instead. Seems that file contents play a role in
|
||||
# order in sets/dictionaries?
|
||||
self.apply_user_action(["RenameColumn", "Table1", "A", "A2"])
|
||||
self.apply_user_action(["RenameColumn", "Table1", "A2", "A_B"])
|
||||
|
||||
# Summary tables are automatically renamed to match the new column names.
|
||||
summary_by_a_and_b = summary_by_a_and_b._replace(tableId="Table1_summary_A_B_B")
|
||||
summary_by_a = summary_by_a._replace(tableId="Table1_summary_A_B2")
|
||||
|
||||
table1.columns[0] = table1.columns[0]._replace(colId="A_B")
|
||||
summary_by_a_and_b.columns[0] = summary_by_a_and_b.columns[0]._replace(colId="A_B")
|
||||
summary_by_a.columns[0] = summary_by_a.columns[0]._replace(colId="A_B")
|
||||
|
||||
table1.columns.extend([
|
||||
Column(21, "summary_ref1", "RefList:Table1_summary_A_B_B", True,
|
||||
"Table1_summary_A_B_B.lookupRecords(A_B=1)", 0),
|
||||
Column(22, "summary_ref2", "Ref:Table1_summary_A_B2", True,
|
||||
"Table1_summary_A_B2.lookupOne(A_B=23)", 0),
|
||||
])
|
||||
|
||||
tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a]
|
||||
self.assertTables(tables)
|
||||
|
||||
def test_source_table_rename_conflict(self):
|
||||
sample = testutil.parse_test_sample({
|
||||
"SCHEMA": [
|
||||
[1, "Table1", [
|
||||
[11, "A", "Text", False, "", "A", ""],
|
||||
]],
|
||||
[2, "Table2_summary", [
|
||||
[13, "A", "Text", False, "", "A", ""],
|
||||
]],
|
||||
],
|
||||
"DATA": {}
|
||||
})
|
||||
self.load_sample(sample)
|
||||
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
|
||||
|
||||
table1 = Table(
|
||||
1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[
|
||||
Column(11, "A", "Text", False, "", 0),
|
||||
],
|
||||
)
|
||||
|
||||
fake_summary = Table(
|
||||
2, "Table2_summary", primaryViewId=0, summarySourceTable=0, columns=[
|
||||
Column(13, "A", "Text", False, "", 0),
|
||||
],
|
||||
)
|
||||
|
||||
summary = Table(
|
||||
3, "Table1_summary", primaryViewId=0, summarySourceTable=1, columns=[
|
||||
Column(14, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(15, "count", "Int", True, "len($group)", 0),
|
||||
],
|
||||
)
|
||||
|
||||
tables = [table1, fake_summary, summary]
|
||||
self.assertTables(tables)
|
||||
|
||||
self.apply_user_action(["RenameTable", "Table1", "Table2"])
|
||||
|
||||
table1 = table1._replace(tableId="Table2")
|
||||
# Summary table is automatically renamed to match the new table name.
|
||||
# Needs a '2' to disambiguate from the fake_summary table.
|
||||
summary = summary._replace(tableId="Table2_summary2")
|
||||
summary.columns[0] = summary.columns[0]._replace(type="RefList:Table2")
|
||||
|
||||
tables = [table1, fake_summary, summary]
|
||||
self.assertTables(tables)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
def test_restrictions(self):
|
||||
@@ -342,7 +481,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
|
||||
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="all", data=[
|
||||
[ "id", "city", "state", "group", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. ],
|
||||
@@ -357,48 +496,49 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
|
||||
# (1) no adding/removing/renaming non-formula columns.
|
||||
with self.assertRaisesRegex(ValueError, r'non-formula column'):
|
||||
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo",
|
||||
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo",
|
||||
{"type": "Numeric", "isFormula": False}])
|
||||
|
||||
with self.assertRaisesRegex(ValueError, r'group-by column'):
|
||||
self.apply_user_action(["RemoveColumn", "GristSummary_7_Address", "state"])
|
||||
self.apply_user_action(["RemoveColumn", "Address_summary_city_state", "state"])
|
||||
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'):
|
||||
self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "st"])
|
||||
self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "st"])
|
||||
|
||||
# (2) no converting between formula/non-formula
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'):
|
||||
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount",
|
||||
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount",
|
||||
{"isFormula": False}])
|
||||
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'):
|
||||
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "state",
|
||||
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "state",
|
||||
{"isFormula": True}])
|
||||
|
||||
# (3) no editing values in non-formula columns
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot enter data .* group-by'):
|
||||
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 6, {"state": "ny"}])
|
||||
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 6, {"state": "ny"}])
|
||||
|
||||
# (4) no removing rows (this is questionable b/c empty rows might be OK to remove)
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot remove record .* summary'):
|
||||
self.apply_user_action(["RemoveRecord", "GristSummary_7_Address", 6])
|
||||
self.apply_user_action(["RemoveRecord", "Address_summary_city_state", 6])
|
||||
|
||||
# (5) no renaming summary tables.
|
||||
with self.assertRaisesRegex(ValueError, r'cannot rename .* summary'):
|
||||
self.apply_user_action(["RenameTable", "GristSummary_7_Address", "GristSummary_hello"])
|
||||
self.apply_user_action(["RenameTable", "Address_summary_city_state", "Address_summary_X"])
|
||||
|
||||
# Check that we can add an empty column, then set a formula for it.
|
||||
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo", {}])
|
||||
self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "foo", {"formula": "1+1"}])
|
||||
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo", {}])
|
||||
self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "foo",
|
||||
{"formula": "1+1"}])
|
||||
with self.assertRaisesRegex(ValueError, "Can't save .* to formula"):
|
||||
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo": "hello"}])
|
||||
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo": "hello"}])
|
||||
|
||||
# But we cannot add an empty column, then add a value to it.
|
||||
self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo2", {}])
|
||||
self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo2", {}])
|
||||
with self.assertRaisesRegex(ValueError, r'Cannot change .* between formula and data'):
|
||||
self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo2": "hello"}])
|
||||
self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo2": "hello"}])
|
||||
|
||||
self.assertTableData('GristSummary_7_Address', cols="all", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="all", data=[
|
||||
[ "id", "city", "state", "group", "count", "amount", "foo", "foo2" ],
|
||||
[ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , 2 , None ],
|
||||
[ 2, "Albany", "NY" , [22], 1, 2. , 2 , None ],
|
||||
@@ -426,7 +566,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# We should have a single summary table, and a single section referring to it.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -442,7 +582,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=18),
|
||||
])
|
||||
])])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
|
||||
|
||||
# Verify more fields of some of the new column objects.
|
||||
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
|
||||
@@ -457,7 +597,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #2 is gone at this point, since it's unused.
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(19, "state", "Text", False, "", 12),
|
||||
Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(21, "count", "Int", True, "len($group)", 0),
|
||||
@@ -471,14 +611,14 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=22),
|
||||
])
|
||||
])])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
[ 3, "IL", 1, 4. ],
|
||||
[ 4, "MA", 2, 5.+9 ],
|
||||
])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_state'])
|
||||
|
||||
# Verify more fields of some of the new column objects.
|
||||
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
|
||||
@@ -492,7 +632,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #3 is gone at this point, since it's unused.
|
||||
Table(4, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(4, "Address_summary_city", 0, 1, columns=[
|
||||
Column(23, "city", "Text", False, "", 11),
|
||||
Column(24, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(25, "count", "Int", True, "len($group)", 0),
|
||||
@@ -506,7 +646,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=26),
|
||||
])
|
||||
])])
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city', cols="subset", data=[
|
||||
[ "id", "city", "count", "amount" ],
|
||||
[ 1, "New York", 3, 1.+6+11 ],
|
||||
[ 2, "Albany", 1, 2. ],
|
||||
@@ -517,7 +657,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 7, "Boston", 1, 9. ],
|
||||
[ 8, "Yonkers", 1, 10. ],
|
||||
])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city'])
|
||||
|
||||
# Verify more fields of some of the new column objects.
|
||||
self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
|
||||
@@ -531,7 +671,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #4 is gone at this point, since it's unused.
|
||||
Table(5, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(5, "Address_summary", 0, 1, columns=[
|
||||
Column(27, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(28, "count", "Int", True, "len($group)", 0),
|
||||
Column(29, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -543,18 +683,18 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=29),
|
||||
])
|
||||
])])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="subset", data=[
|
||||
self.assertTableData('Address_summary', cols="subset", data=[
|
||||
[ "id", "count", "amount"],
|
||||
[ 1, 11, 66.0 ],
|
||||
])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary'])
|
||||
|
||||
# Back to full circle, but with group-by columns differently arranged.
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 2, [12,11]])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #5 is gone at this point, since it's unused.
|
||||
Table(6, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(6, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(30, "state", "Text", False, "", 12),
|
||||
Column(31, "city", "Text", False, "", 11),
|
||||
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -570,7 +710,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=34),
|
||||
])
|
||||
])])
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_city_state', cols="subset", data=[
|
||||
[ "id", "city", "state", "count", "amount" ],
|
||||
[ 1, "New York", "NY" , 3, 1.+6+11 ],
|
||||
[ 2, "Albany", "NY" , 1, 2. ],
|
||||
@@ -582,13 +722,13 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, "Boston", "MA" , 1, 9. ],
|
||||
[ 9, "Yonkers", "NY" , 1, 10. ],
|
||||
])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
|
||||
|
||||
# Now add a different view section with the same group-by columns.
|
||||
self.apply_user_action(["CreateViewSection", 1, 1, "record", [11,12], None])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(6, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(6, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(30, "state", "Text", False, "", 12),
|
||||
Column(31, "city", "Text", False, "", 11),
|
||||
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -610,20 +750,20 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(27, colRef=34),
|
||||
])
|
||||
])])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
|
||||
|
||||
# Change one view section, and ensure there are now two summary tables.
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 7, []])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(6, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(6, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(30, "state", "Text", False, "", 12),
|
||||
Column(31, "city", "Text", False, "", 11),
|
||||
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(33, "count", "Int", True, "len($group)", 0),
|
||||
Column(34, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(7, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(7, "Address_summary", 0, 1, columns=[
|
||||
Column(35, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(36, "count", "Int", True, "len($group)", 0),
|
||||
Column(37, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -641,15 +781,15 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(27, colRef=37),
|
||||
])
|
||||
])])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address',
|
||||
'#summary#GristSummary_7_Address2'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state',
|
||||
'#summary#Address_summary'])
|
||||
|
||||
# Delete one view section, and see that the summary table is gone.
|
||||
self.apply_user_action(["RemoveViewSection", 7])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #7 is gone at this point, since it's now unused.
|
||||
Table(6, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(6, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(30, "state", "Text", False, "", 12),
|
||||
Column(31, "city", "Text", False, "", 11),
|
||||
Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -665,14 +805,14 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=34),
|
||||
])
|
||||
])])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address'])
|
||||
self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state'])
|
||||
|
||||
# Change the section to add and then remove the "amount" to the group-by column; check that
|
||||
# column "amount" was correctly restored
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 2, [11, 12, 13]])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(7, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(7, "Address_summary_amount_city_state", 0, 1, columns=[
|
||||
Column(35, "city", "Text", False, "", 11),
|
||||
Column(36, "state", "Text", False, "", 12),
|
||||
Column(37, "amount", "Numeric", False, "", 13),
|
||||
@@ -691,7 +831,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(8, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(8, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(40, "city", "Text", False, "", 11),
|
||||
Column(41, "state", "Text", False, "", 12),
|
||||
Column(42, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -715,7 +855,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 2, [11]])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(9, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(9, "Address_summary_city", 0, 1, columns=[
|
||||
Column(45, "city", "Text", False, "", 11),
|
||||
Column(46, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
Column(48, "count", "Int", True, "len($group)", 0),
|
||||
@@ -741,13 +881,13 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
|
||||
self.load_sample(self.sample)
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None])
|
||||
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "city",
|
||||
self.apply_user_action(["AddVisibleColumn", "Address_summary_state", "city",
|
||||
{"formula": "$state.lower()"}])
|
||||
|
||||
# We should have a single summary table, and a single section referring to it.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_state", 0, 1, columns=[
|
||||
Column(14, "state", "Text", False, "", 12),
|
||||
Column(15, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(16, "count", "Int", True, "len($group)", 0),
|
||||
@@ -763,7 +903,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Field(8, colRef=18),
|
||||
])
|
||||
])])
|
||||
self.assertTableData('GristSummary_7_Address', cols="subset", data=[
|
||||
self.assertTableData('Address_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" , "city"],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 , "ny" ],
|
||||
[ 2, "WA", 1, 3. , "wa" ],
|
||||
@@ -775,7 +915,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]])
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(19, "city", "Text", False, "", 11),
|
||||
Column(20, "state", "Text", False, "", 12),
|
||||
Column(21, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -812,19 +952,19 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# We should have a single summary table, and a single section referring to it.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(17, "count", "Int", True, "len($group)", 0),
|
||||
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
]),
|
||||
Table(4, "GristSummary_7_Address3", 0, 1, columns=[
|
||||
Table(4, "Address_summary_state", 0, 1, columns=[
|
||||
Column(22, "state", "Text", False, "", 12),
|
||||
Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(24, "count", "Int", True, "len($group)", 0),
|
||||
@@ -862,7 +1002,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# Verify that unused summary tables are also gone, but the one used remains.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -893,7 +1033,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# We should have a single summary table, and a single (non-raw) section referring to it.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -912,7 +1052,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
# Note that Table #2 is gone at this point, since it's unused.
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(19, "state", "Text", False, "", 12),
|
||||
Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(21, "count", "Int", True, "len($group)", 0),
|
||||
@@ -935,13 +1075,13 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None])
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None])
|
||||
# Add a formula column
|
||||
self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average",
|
||||
self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average",
|
||||
{"formula": "$amount / $count"}])
|
||||
|
||||
# Check the table and columns for all the summary tables.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(2, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(2, "Address_summary_city_state", 0, 1, columns=[
|
||||
Column(14, "city", "Text", False, "", 11),
|
||||
Column(15, "state", "Text", False, "", 12),
|
||||
Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
@@ -949,7 +1089,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
Column(22, "average", "Any", True, "$amount / $count", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -986,7 +1126,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
# Check the table and columns for all the summary tables.
|
||||
self.assertTables([
|
||||
self.starting_table,
|
||||
Table(3, "GristSummary_7_Address2", 0, 1, columns=[
|
||||
Table(3, "Address_summary", 0, 1, columns=[
|
||||
Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(20, "count", "Int", True, "len($group)", 0),
|
||||
Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0),
|
||||
@@ -1039,7 +1179,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, 8.0, "Boston", "MA" , [29], 1, 9. , 9. ],
|
||||
[ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. , 10. ],
|
||||
])
|
||||
self.assertTableData('GristSummary_7_Address2', cols="all", data=[
|
||||
self.assertTableData('Address_summary', cols="all", data=[
|
||||
[ "id", "count", "amount", "group" ],
|
||||
[ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]],
|
||||
])
|
||||
@@ -1069,7 +1209,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
Column(24, "group", "RefList:Address", True,
|
||||
"Address.lookupRecords(city=$city, state=$state)", 0),
|
||||
]),
|
||||
Table(4, "GristSummary_6_Table1", primaryViewId=0, summarySourceTable=3, columns=[
|
||||
Table(4, "Table1_summary_state", primaryViewId=0, summarySourceTable=3, columns=[
|
||||
Column(25, "state", "Text", False, "", 21),
|
||||
Column(26, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(27, "count", "Int", True, "SUM($group.count)", 0),
|
||||
@@ -1090,7 +1230,7 @@ class TestSummary2(test_engine.EngineTestCase):
|
||||
[ 8, 8.0, "Boston", "MA" , [29], 1, 9. ],
|
||||
[ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. ],
|
||||
])
|
||||
self.assertTableData('GristSummary_6_Table1', cols="all", data=[
|
||||
self.assertTableData('Table1_summary_state', cols="all", data=[
|
||||
[ "id", "state", "group", "count", "amount" ],
|
||||
[ 1, "NY", [1,2,6,7,9], 7, 1.+6+11+2+7+8+10 ],
|
||||
[ 2, "WA", [3], 1, 3. ],
|
||||
|
||||
@@ -46,7 +46,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
|
||||
|
||||
summary_table1 = Table(
|
||||
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
|
||||
2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(14, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
|
||||
@@ -60,7 +60,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
|
||||
|
||||
summary_table2 = Table(
|
||||
3, "GristSummary_6_Source2", primaryViewId=0, summarySourceTable=1,
|
||||
3, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(16, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(17, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12),
|
||||
@@ -75,7 +75,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10], None])
|
||||
|
||||
summary_table3 = Table(
|
||||
4, "GristSummary_6_Source3", primaryViewId=0, summarySourceTable=1,
|
||||
4, "Source_summary_other", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(20, "other", "Text", isFormula=False, formula="", summarySourceCol=10),
|
||||
Column(21, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
|
||||
@@ -89,7 +89,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10, 11], None])
|
||||
|
||||
summary_table4 = Table(
|
||||
5, "GristSummary_6_Source4", primaryViewId=0, summarySourceTable=1,
|
||||
5, "Source_summary_choices1_other", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(23, "other", "Text", isFormula=False, formula="", summarySourceCol=10),
|
||||
Column(24, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||
@@ -105,13 +105,13 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
)
|
||||
|
||||
# Verify the summarized data.
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
[1, "a", [21], 1],
|
||||
[2, "b", [21], 1],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source2', data=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
[1, "a", "c", [21], 1],
|
||||
[2, "a", "d", [21], 1],
|
||||
@@ -119,12 +119,12 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
[4, "b", "d", [21], 1],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source3', data=[
|
||||
self.assertTableData('Source_summary_other', data=[
|
||||
["id", "other", "group", "count"],
|
||||
[1, "foo", [21], 1],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source4', data=[
|
||||
self.assertTableData('Source_summary_choices1_other', data=[
|
||||
["id", "other", "choices1", "group", "count"],
|
||||
[1, "foo", "a", [21], 1],
|
||||
[2, "foo", "b", [21], 1],
|
||||
@@ -132,28 +132,30 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
|
||||
# Verify the optimisation works for the table without choicelists
|
||||
self.assertIs(self.engine.tables["Source"]._summary_simple, None)
|
||||
self.assertIs(self.engine.tables["GristSummary_6_Source"]._summary_simple, False)
|
||||
self.assertIs(self.engine.tables["GristSummary_6_Source2"]._summary_simple, False)
|
||||
self.assertIs(self.engine.tables["Source_summary_choices1"]._summary_simple, False)
|
||||
self.assertIs(self.engine.tables["Source_summary_choices1_choices2"]._summary_simple, False)
|
||||
# simple summary and lookup
|
||||
self.assertIs(self.engine.tables["GristSummary_6_Source3"]._summary_simple, True)
|
||||
self.assertIs(self.engine.tables["GristSummary_6_Source4"]._summary_simple, False)
|
||||
self.assertIs(self.engine.tables["Source_summary_other"]._summary_simple, True)
|
||||
self.assertIs(self.engine.tables["Source_summary_choices1_other"]._summary_simple, False)
|
||||
|
||||
self.assertEqual(
|
||||
{k: type(v) for k, v in self.engine.tables["Source"]._special_cols.items()},
|
||||
{
|
||||
'#summary#GristSummary_6_Source': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#GristSummary_6_Source', match_empty=no_match_empty)":
|
||||
'#summary#Source_summary_choices1': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#Source_summary_choices1', match_empty=no_match_empty)":
|
||||
lookup.ContainsLookupMapColumn,
|
||||
'#summary#GristSummary_6_Source2': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#GristSummary_6_Source2', match_empty=no_match_empty)":
|
||||
'#summary#Source_summary_choices1_choices2': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#Source_summary_choices1_choices2', "
|
||||
"match_empty=no_match_empty)":
|
||||
lookup.ContainsLookupMapColumn,
|
||||
|
||||
# simple summary and lookup
|
||||
'#summary#GristSummary_6_Source3': column.ReferenceColumn,
|
||||
'#lookup##summary#GristSummary_6_Source3': lookup.SimpleLookupMapColumn,
|
||||
'#summary#Source_summary_other': column.ReferenceColumn,
|
||||
'#lookup##summary#Source_summary_other': lookup.SimpleLookupMapColumn,
|
||||
|
||||
'#summary#GristSummary_6_Source4': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#GristSummary_6_Source4', match_empty=no_match_empty)":
|
||||
'#summary#Source_summary_choices1_other': column.ReferenceListColumn,
|
||||
"#lookup#_Contains(value='#summary#Source_summary_choices1_other', "
|
||||
"match_empty=no_match_empty)":
|
||||
lookup.ContainsLookupMapColumn,
|
||||
|
||||
"#lookup#": lookup.SimpleLookupMapColumn,
|
||||
@@ -169,12 +171,12 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
])
|
||||
|
||||
# Verify that the summary table rows containing 'b' are removed
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
[1, "a", [21], 1],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source2', data=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
[1, "a", "c", [21], 1],
|
||||
[2, "a", "d", [21], 1],
|
||||
@@ -184,13 +186,13 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.update_record("Source", 21, choices2=["L", "c", "d", "e"])
|
||||
|
||||
# First summary table unaffected
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
[1, "a", [21], 1],
|
||||
])
|
||||
|
||||
# New row added for 'e'
|
||||
self.assertTableData('GristSummary_6_Source2', data=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
[1, "a", "c", [21], 1],
|
||||
[2, "a", "d", [21], 1],
|
||||
@@ -205,12 +207,12 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
[21, None, ["c", "d", "e"], "foo"],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
[2, "", [21], 1],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source2', data=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
[4, "", "c", [21], 1],
|
||||
[5, "", "d", [21], 1],
|
||||
@@ -221,11 +223,11 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.remove_record("Source", 21)
|
||||
|
||||
# All summary rows are now empty and thus removed
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
])
|
||||
|
||||
self.assertTableData('GristSummary_6_Source2', data=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
])
|
||||
|
||||
@@ -263,7 +265,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
])
|
||||
|
||||
# Summary tables now have an even distribution of combinations
|
||||
self.assertTableData('GristSummary_6_Source', data=[
|
||||
self.assertTableData('Source_summary_choices1', data=[
|
||||
["id", "choices1", "group", "count"],
|
||||
[1, "a", [101, 103, 104, 106, 107, 109], 6],
|
||||
[2, "b", [102, 103, 105, 106, 108, 109], 6],
|
||||
@@ -279,7 +281,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
[5, "", "", [110], 1],
|
||||
]
|
||||
|
||||
self.assertTableData('GristSummary_6_Source2', data=summary_data)
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=summary_data)
|
||||
|
||||
# Verify that "DetachSummaryViewSection" useraction works correctly.
|
||||
self.apply_user_action(["DetachSummaryViewSection", 4])
|
||||
@@ -336,7 +338,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
|
||||
|
||||
summary_table = Table(
|
||||
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
|
||||
2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(12, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(13, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
|
||||
@@ -353,7 +355,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
]
|
||||
|
||||
self.assertTables([starting_table, summary_table])
|
||||
self.assertTableData('GristSummary_6_Source', data=data)
|
||||
self.assertTableData('Source_summary_choices1', data=data)
|
||||
|
||||
# Change the column from Choice to ChoiceList
|
||||
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 11, {"type": "ChoiceList"}])
|
||||
@@ -365,7 +367,7 @@ 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)
|
||||
self.assertTableData('Source_summary_choices1', data=data)
|
||||
|
||||
def test_rename_choices(self):
|
||||
self.load_sample(self.sample)
|
||||
@@ -374,7 +376,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
|
||||
|
||||
summary_table = Table(
|
||||
2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1,
|
||||
2, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1,
|
||||
columns=[
|
||||
Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11),
|
||||
Column(14, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12),
|
||||
@@ -397,17 +399,17 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {'stored': [
|
||||
['UpdateRecord', 'Source', 21, {'choices1': ['L', u'aa', u'bb']}],
|
||||
['BulkAddRecord',
|
||||
'GristSummary_6_Source',
|
||||
'Source_summary_choices1_choices2',
|
||||
[5, 6, 7, 8],
|
||||
{'choices1': [u'aa', u'aa', u'bb', u'bb'],
|
||||
'choices2': [u'c', u'd', u'c', u'd']}],
|
||||
['BulkRemoveRecord', 'GristSummary_6_Source', [1, 2, 3, 4]],
|
||||
['BulkRemoveRecord', 'Source_summary_choices1_choices2', [1, 2, 3, 4]],
|
||||
['BulkUpdateRecord',
|
||||
'GristSummary_6_Source',
|
||||
'Source_summary_choices1_choices2',
|
||||
[5, 6, 7, 8],
|
||||
{'count': [1, 1, 1, 1]}],
|
||||
['BulkUpdateRecord',
|
||||
'GristSummary_6_Source',
|
||||
'Source_summary_choices1_choices2',
|
||||
[5, 6, 7, 8],
|
||||
{'group': [['L', 21],
|
||||
['L', 21],
|
||||
@@ -423,7 +425,7 @@ class TestSummaryChoiceList(EngineTestCase):
|
||||
|
||||
# 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=[
|
||||
self.assertTableData('Source_summary_choices1_choices2', data=[
|
||||
["id", "choices1", "choices2", "group", "count"],
|
||||
[9, "aa", "cc", [21], 1],
|
||||
[10, "aa", "dd", [21], 1],
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
|
||||
self.load_sample(self.sample)
|
||||
# Create a summary section, grouped by the "State" column.
|
||||
self.apply_user_action(["CreateViewSection", 1, 0, "record", [1], None])
|
||||
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
|
||||
self.assertTableData('Person_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count"],
|
||||
[ 1, "NY", 2],
|
||||
[ 2, "IL", 2],
|
||||
@@ -39,7 +39,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
|
||||
])
|
||||
|
||||
out_actions = self.update_record('Person', 4, state='ME')
|
||||
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
|
||||
self.assertTableData('Person_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count"],
|
||||
[ 1, "NY", 1],
|
||||
[ 2, "IL", 2],
|
||||
@@ -47,7 +47,7 @@ class TestSummaryUndo(test_engine.EngineTestCase):
|
||||
])
|
||||
|
||||
self.apply_undo_actions(out_actions.undo[0:1])
|
||||
self.assertTableData('GristSummary_6_Person', cols="subset", data=[
|
||||
self.assertTableData('Person_summary_state', cols="subset", data=[
|
||||
[ "id", "state", "count"],
|
||||
[ 1, "NY", 2],
|
||||
[ 2, "IL", 2],
|
||||
|
||||
@@ -71,7 +71,7 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
Column(7, "address", "Ref:Address", False, "", 0),
|
||||
Column(8, "city", "Any", True, "$address.city", 0),
|
||||
]),
|
||||
Table(3, "GristSummary_7_Address", 0, 1, columns=[
|
||||
Table(3, "Address_summary_state", 0, 1, columns=[
|
||||
Column(9, "state", "Text", False, "", summarySourceCol=3),
|
||||
Column(10, "group", "RefList:Address", True, summarySourceCol=0,
|
||||
formula="table.getSummarySourceGroup(rec)"),
|
||||
@@ -116,7 +116,7 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
# Verify the data we've loaded.
|
||||
self.assertTableData('Address', cols="subset", data=self.address_table_data)
|
||||
self.assertTableData('People', cols="subset", data=self.people_table_data)
|
||||
self.assertTableData("GristSummary_7_Address", cols="subset", data=[
|
||||
self.assertTableData("Address_summary_state", cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
@@ -142,7 +142,7 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
["id", "tableId"],
|
||||
[1, "Location"],
|
||||
[2, "Persons"],
|
||||
[3, "GristSummary_8_Location"],
|
||||
[3, "Location_summary_state"],
|
||||
])
|
||||
|
||||
# Check that reference columns to renamed tables get their type modified.
|
||||
@@ -161,14 +161,14 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
["ModifyColumn", "Persons", "address", {"type": "Int"}],
|
||||
["ModifyColumn", "GristSummary_8_Location", "group", {"type": "Int"}],
|
||||
["ModifyColumn", "Location_summary_state", "group", {"type": "Int"}],
|
||||
["RenameTable", "Location", "A2"],
|
||||
["RenameTable", "GristSummary_8_Location", "GristSummary_2_A2"],
|
||||
["RenameTable", "Location_summary_state", "A2_summary_state"],
|
||||
["RenameTable", "Persons", "A3"],
|
||||
["BulkUpdateRecord", "_grist_Tables", [1, 3, 2],
|
||||
{"tableId": ["A2", "GristSummary_2_A2", "A3"]}],
|
||||
{"tableId": ["A2", "A2_summary_state", "A3"]}],
|
||||
["ModifyColumn", "A3", "address", {"type": "Ref:A2"}],
|
||||
["ModifyColumn", "GristSummary_2_A2", "group", {"type": "RefList:A2"}],
|
||||
["ModifyColumn", "A2_summary_state", "group", {"type": "RefList:A2"}],
|
||||
["BulkUpdateRecord", "_grist_Tables_column", [7, 10], {"type": ["Ref:A2", "RefList:A2"]}],
|
||||
]
|
||||
})
|
||||
@@ -178,7 +178,7 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
["id", "tableId"],
|
||||
[1, "A2"],
|
||||
[2, "A3"],
|
||||
[3, "GristSummary_2_A2"],
|
||||
[3, "A2_summary_state"],
|
||||
[4, "A"],
|
||||
])
|
||||
|
||||
@@ -192,7 +192,7 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
# Verify the data we've loaded.
|
||||
self.assertTableData('A2', cols="subset", data=self.address_table_data)
|
||||
self.assertTableData('A3', cols="subset", data=self.people_table_data)
|
||||
self.assertTableData("GristSummary_2_A2", cols="subset", data=[
|
||||
self.assertTableData("A2_summary_state", cols="subset", data=[
|
||||
[ "id", "state", "count", "amount" ],
|
||||
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
||||
[ 2, "WA", 1, 3. ],
|
||||
@@ -226,15 +226,15 @@ class TestTableActions(test_engine.EngineTestCase):
|
||||
self.assertPartialOutActions(out_actions, {
|
||||
"stored": [
|
||||
["ModifyColumn", "People", "address", {"type": "Int"}],
|
||||
["ModifyColumn", "GristSummary_7_Address", "group", {"type": "Int"}],
|
||||
["ModifyColumn", "GristSummary_6_People", "address", {"type": "Int"}],
|
||||
["ModifyColumn", "Address_summary_state", "group", {"type": "Int"}],
|
||||
["ModifyColumn", "People_summary_address", "address", {"type": "Int"}],
|
||||
["RenameTable", "Address", "Location"],
|
||||
["RenameTable", "GristSummary_7_Address", "GristSummary_8_Location"],
|
||||
["RenameTable", "Address_summary_state", "Location_summary_state"],
|
||||
["BulkUpdateRecord", "_grist_Tables", [1, 3],
|
||||
{"tableId": ["Location", "GristSummary_8_Location"]}],
|
||||
{"tableId": ["Location", "Location_summary_state"]}],
|
||||
["ModifyColumn", "People", "address", {"type": "Ref:Location"}],
|
||||
["ModifyColumn", "GristSummary_8_Location", "group", {"type": "RefList:Location"}],
|
||||
["ModifyColumn", "GristSummary_6_People", "address", {"type": "Ref:Location"}],
|
||||
["ModifyColumn", "Location_summary_state", "group", {"type": "RefList:Location"}],
|
||||
["ModifyColumn", "People_summary_address", "address", {"type": "Ref:Location"}],
|
||||
["BulkUpdateRecord", "_grist_Tables_column", [7, 10, 13],
|
||||
{"type": ["Ref:Location", "RefList:Location", "Ref:Location"]}],
|
||||
]
|
||||
|
||||
@@ -156,7 +156,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
|
||||
# Create another section for the same view, this time summarized.
|
||||
self.apply_user_action(["CreateViewSection", 1, 1, "record", [21], None])
|
||||
summary_table = Table(2, "GristSummary_7_Address", 0, summarySourceTable=1, columns=[
|
||||
summary_table = Table(2, "Address_summary_city", 0, summarySourceTable=1, columns=[
|
||||
Column(22, "city", "Text", isFormula=False, formula="", summarySourceCol=21),
|
||||
Column(23, "group", "RefList:Address", isFormula=True,
|
||||
formula="table.getSummarySourceGroup(rec)", summarySourceCol=0),
|
||||
@@ -248,7 +248,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
Column(33, "C", "Any", isFormula=True, formula="", summarySourceCol=0),
|
||||
])
|
||||
# A summary of it.
|
||||
summary_table = Table(5, "GristSummary_6_Table3", 0, summarySourceTable=4, columns=[
|
||||
summary_table = Table(5, "Table3_summary", 0, summarySourceTable=4, columns=[
|
||||
Column(34, "group", "RefList:Table3", isFormula=True,
|
||||
formula="table.getSummarySourceGroup(rec)", summarySourceCol=0),
|
||||
Column(35, "count", "Int", isFormula=True, formula="len($group)", summarySourceCol=0),
|
||||
@@ -287,7 +287,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
Column(3, "state", "Text", False, "", 0),
|
||||
Column(4, "size", "Numeric", False, "", 0),
|
||||
]),
|
||||
Table(2, "GristSummary_7_Schools", 0, 1, columns=[
|
||||
Table(2, "Schools_summary_state", 0, 1, columns=[
|
||||
Column(5, "state", "Text", False, "", 3),
|
||||
Column(6, "group", "RefList:Schools", True, "table.getSummarySourceGroup(rec)", 0),
|
||||
Column(7, "count", "Int", True, "len($group)", 0),
|
||||
@@ -398,7 +398,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
[ 'id', 'tableId', 'primaryViewId' ],
|
||||
[ 1, 'Schools', 1],
|
||||
[ 2, 'GristSummary_7_Schools', 0],
|
||||
[ 2, 'Schools_summary_state', 0],
|
||||
[ 3, 'Table1', 0],
|
||||
])
|
||||
self.assertTableData('_grist_Views', cols="subset", data=[
|
||||
@@ -416,7 +416,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
[ 'id', 'tableId', 'primaryViewId' ],
|
||||
[ 1, 'Schools', 1],
|
||||
[ 2, 'GristSummary_7_Schools', 0],
|
||||
[ 2, 'Schools_summary_state', 0],
|
||||
[ 3, 'Table1', 0],
|
||||
])
|
||||
self.assertTableData('_grist_Views', cols="subset", data=[
|
||||
@@ -434,7 +434,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
['id', 'tableId'],
|
||||
[1, 'Bars', 1],
|
||||
[2, 'GristSummary_4_Bars', 0],
|
||||
[2, 'Bars_summary_state', 0],
|
||||
[3, 'Table1', 0],
|
||||
])
|
||||
self.assertTableData('_grist_Views', cols="subset", data=[
|
||||
@@ -452,7 +452,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
['id', 'tableId'],
|
||||
[1, 'A', 1],
|
||||
[2, 'GristSummary_1_A', 0],
|
||||
[2, 'A_summary_state', 0],
|
||||
[3, 'Table1', 0],
|
||||
])
|
||||
self.assertTableData('_grist_Views', cols="subset", data=[
|
||||
@@ -468,7 +468,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
|
||||
[1, 'Z', 1, 2],
|
||||
[2, 'GristSummary_1_Z', 0, 4],
|
||||
[2, 'Z_summary_state', 0, 4],
|
||||
[3, 'Table1', 0, 7],
|
||||
])
|
||||
self.assertTableData('_grist_Views', cols="subset", data=[
|
||||
@@ -485,7 +485,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'],
|
||||
[1, 'Z', 1, 2],
|
||||
[2, 'GristSummary_1_Z', 0, 4],
|
||||
[2, 'Z_summary_state', 0, 4],
|
||||
[3, 'Table1', 0, 7],
|
||||
[4, 'Stations', 4, 10],
|
||||
])
|
||||
@@ -513,7 +513,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
['id', 'tableId'],
|
||||
[1, 'Schools'],
|
||||
[2, 'GristSummary_7_Schools'],
|
||||
[2, 'Schools_summary_state'],
|
||||
[3, 'Table1'],
|
||||
[4, 'Stations'],
|
||||
])
|
||||
@@ -618,7 +618,7 @@ class TestUserActions(test_engine.EngineTestCase):
|
||||
self.assertTableData('_grist_Tables', cols="subset", data=[
|
||||
[ 'id', 'tableId', 'primaryViewId' ],
|
||||
[ 1, 'Schools', 1],
|
||||
[ 2, 'GristSummary_7_Schools', 0],
|
||||
[ 2, 'Schools_summary_state', 0],
|
||||
[ 3, 'C', 0],
|
||||
])
|
||||
|
||||
|
||||
@@ -561,7 +561,7 @@ class UserActions(object):
|
||||
update_pairs.append((rec, values))
|
||||
if has_diff_value(values, 'tableId', rec.tableId):
|
||||
# Disallow renaming of summary tables.
|
||||
if rec.summarySourceTable:
|
||||
if rec.summarySourceTable and self._indirection_level == DIRECT_ACTION:
|
||||
raise ValueError("RenameTable: cannot rename a summary table")
|
||||
|
||||
# Find a non-conflicting name, except that we don't need to avoid the old name.
|
||||
@@ -572,7 +572,8 @@ class UserActions(object):
|
||||
if new_table_id != rec.tableId:
|
||||
# If there are summary tables based on this table, rename them to appropriate names.
|
||||
for st in rec.summaryTables:
|
||||
st_table_id = summary.encode_summary_table_name(new_table_id)
|
||||
groupby_col_ids = [c.colId for c in st.columns if c.summarySourceCol]
|
||||
st_table_id = summary.encode_summary_table_name(new_table_id, groupby_col_ids)
|
||||
st_table_id = identifiers.pick_table_ident(st_table_id, avoid=avoid_tableid_set)
|
||||
avoid_tableid_set.add(st_table_id)
|
||||
update_pairs.append((st, {'tableId': st_table_id}))
|
||||
@@ -697,6 +698,7 @@ class UserActions(object):
|
||||
|
||||
make_acl_updates = acl.prepare_acl_col_renames(self._docmodel, self, renames)
|
||||
|
||||
rename_summary_tables = set()
|
||||
for c, values in update_pairs:
|
||||
# Trigger ModifyColumn and RenameColumn as necessary
|
||||
schema_colinfo = select_keys(values, _modify_col_schema_props)
|
||||
@@ -704,6 +706,8 @@ class UserActions(object):
|
||||
self.doModifyColumn(c.parentId.tableId, c.colId, schema_colinfo)
|
||||
if has_diff_value(values, 'colId', c.colId):
|
||||
self._do_doc_action(actions.RenameColumn(c.parentId.tableId, c.colId, values['colId']))
|
||||
if c.summarySourceCol:
|
||||
rename_summary_tables.add(c.parentId)
|
||||
|
||||
# If we change a column's type, we should ALSO unset each affected field's displayCol.
|
||||
type_changed = [c for c, values in update_pairs if has_diff_value(values, 'type', c.type)]
|
||||
@@ -718,6 +722,12 @@ class UserActions(object):
|
||||
|
||||
make_acl_updates()
|
||||
|
||||
for table in rename_summary_tables:
|
||||
groupby_col_ids = [c.colId for c in table.columns if c.summarySourceCol]
|
||||
new_table_id = summary.encode_summary_table_name(table.summarySourceTable.tableId,
|
||||
groupby_col_ids)
|
||||
with self.indirect_actions():
|
||||
self.RenameTable(table.tableId, new_table_id)
|
||||
|
||||
@override_action('BulkUpdateRecord', '_grist_Views_section')
|
||||
def _updateViewSections(self, table_id, row_ids, col_values):
|
||||
|
||||
Reference in New Issue
Block a user