Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API does not expose column identifier within reference table #460

Closed
Vinschni opened this issue Mar 12, 2023 · 5 comments
Closed

REST API does not expose column identifier within reference table #460

Vinschni opened this issue Mar 12, 2023 · 5 comments

Comments

@Vinschni
Copy link
Contributor

The REST API does allow to follow a reference column to its original table as the type contains a Ref: prefix, see here an example response for a columns request:

{'id': 'Assignee', 'fields': {'colRef': 140, 'parentId': 11, 'parentPos': 121, 'type': ' Ref: People', 'widgetOptions': '{"widget":"Reference","alignment":"left","wrap":true}', 'isFormula': False, 'formula': '', 'label': 'Assignee', 'untieColIdFromLabel': False, 'summarySourceCol': 0, 'displayCol': 192, 'visibleCol': 2, 'rules': None, 'recalcWhen': 1, 'recalcDeps': None}}

--> But there is no way to know what column within the People table in this case the Assignee field reference to.
The API is not exposing that information, am I correct?

@paulfitz
Copy link
Member

Hi @Vinschni. In Grist, references are always to the row id of the target table. They are not as flexible as foreign keys in that respect.

@Vinschni
Copy link
Contributor Author

Vinschni commented Mar 19, 2023

Hi @paulfitz,
the grist web client obviously knows which column in the references table to show, but searching around in the structure that is returned here: GristDoc.ts:206

just gave me hints. I made a minimal example to understand that response and attached it (Untitled document.zip)

In short: "Table 1" contains a reference column to table "stones" column "stoneName".
There is "$A.stoneName" in a "formula" part of that json, which i assume helps the client understand what column in referenced table to take.

  1. The sorting of the lists in that json seems random and i don't see what order the list indexes in that json follow. I can't relate the reference column in "Table 1" with that column.
  2. Do the gristHelper_Display entries have anything to do with it?
RECEIVED DOC RESPONSE --> Click me
{
    "docFD": 0,
    "clientId": "5d6c66cfb0ad8788",
    "doc": {
        "_grist_DocInfo": [
            "TableData",
            "_grist_DocInfo",
            [
                1
            ],
            {
                "docId": [
                    ""
                ],
                "peers": [
                    ""
                ],
                "basketId": [
                    ""
                ],
                "schemaVersion": [
                    36
                ],
                "timezone": [
                    "Europe/Berlin"
                ],
                "documentSettings": [
                    "{\"locale\":\"de-DE\",\"engine\":\"python3\"}"
                ]
            }
        ],
        "_grist_Tables": [
            "TableData",
            "_grist_Tables",
            [
                1,
                2
            ],
            {
                "tableId": [
                    "Table1",
                    "Stones"
                ],
                "primaryViewId": [
                    1,
                    2
                ],
                "summarySourceTable": [
                    0,
                    0
                ],
                "onDemand": [
                    false,
                    false
                ],
                "rawViewSectionRef": [
                    2,
                    4
                ]
            }
        ],
        "_grist_Tables_column": [
            "TableData",
            "_grist_Tables_column",
            [
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9
            ],
            {
                "parentId": [
                    1,
                    1,
                    1,
                    1,
                    2,
                    2,
                    2,
                    2,
                    1
                ],
                "parentPos": [
                    1,
                    2,
                    3,
                    4,
                    5,
                    6,
                    7,
                    8,
                    9
                ],
                "colId": [
                    "manualSort",
                    "A",
                    "B",
                    "C",
                    "manualSort",
                    "stoneName",
                    "B",
                    "C",
                    "gristHelper_Display"
                ],
                "type": [
                    "ManualSortPos",
                    "Ref:Stones",
                    "Any",
                    "Any",
                    "ManualSortPos",
                    "Text",
                    "Any",
                    "Any",
                    "Any"
                ],
                "widgetOptions": [
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    ""
                ],
                "isFormula": [
                    false,
                    false,
                    true,
                    true,
                    false,
                    false,
                    true,
                    true,
                    true
                ],
                "formula": [
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "$A.stoneName"
                ],
                "label": [
                    "manualSort",
                    "A",
                    "B",
                    "C",
                    "manualSort",
                    "stoneName",
                    "B",
                    "C",
                    "gristHelper_Display"
                ],
                "description": [
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    ""
                ],
                "untieColIdFromLabel": [
                    false,
                    false,
                    false,
                    false,
                    false,
                    false,
                    false,
                    false,
                    false
                ],
                "summarySourceCol": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "displayCol": [
                    0,
                    9,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "visibleCol": [
                    0,
                    6,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "rules": [
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
                ],
                "recalcWhen": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "recalcDeps": [
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
                ]
            }
        ],
        "_grist_Imports": [
            "TableData",
            "_grist_Imports",
            [],
            {
                "tableRef": [],
                "origFileName": [],
                "parseFormula": [],
                "delimiter": [],
                "doublequote": [],
                "escapechar": [],
                "quotechar": [],
                "skipinitialspace": [],
                "encoding": [],
                "hasHeaders": []
            }
        ],
        "_grist_External_database": [
            "TableData",
            "_grist_External_database",
            [],
            {
                "host": [],
                "port": [],
                "username": [],
                "dialect": [],
                "database": [],
                "storage": []
            }
        ],
        "_grist_External_table": [
            "TableData",
            "_grist_External_table",
            [],
            {
                "tableRef": [],
                "databaseRef": [],
                "tableName": []
            }
        ],
        "_grist_TableViews": [
            "TableData",
            "_grist_TableViews",
            [],
            {
                "tableRef": [],
                "viewRef": []
            }
        ],
        "_grist_TabItems": [
            "TableData",
            "_grist_TabItems",
            [],
            {
                "tableRef": [],
                "viewRef": []
            }
        ],
        "_grist_TabBar": [
            "TableData",
            "_grist_TabBar",
            [
                1,
                2
            ],
            {
                "viewRef": [
                    1,
                    2
                ],
                "tabPos": [
                    1,
                    2
                ]
            }
        ],
        "_grist_Pages": [
            "TableData",
            "_grist_Pages",
            [
                1,
                2
            ],
            {
                "viewRef": [
                    1,
                    2
                ],
                "indentation": [
                    0,
                    0
                ],
                "pagePos": [
                    1,
                    2
                ]
            }
        ],
        "_grist_Views": [
            "TableData",
            "_grist_Views",
            [
                1,
                2
            ],
            {
                "name": [
                    "Table1",
                    "stones"
                ],
                "type": [
                    "raw_data",
                    "raw_data"
                ],
                "layoutSpec": [
                    "",
                    ""
                ]
            }
        ],
        "_grist_Views_section": [
            "TableData",
            "_grist_Views_section",
            [
                1,
                2,
                3,
                4
            ],
            {
                "tableRef": [
                    1,
                    1,
                    2,
                    2
                ],
                "parentId": [
                    1,
                    0,
                    2,
                    0
                ],
                "parentKey": [
                    "record",
                    "record",
                    "record",
                    "record"
                ],
                "title": [
                    "",
                    "",
                    "",
                    "stones"
                ],
                "defaultWidth": [
                    100,
                    100,
                    100,
                    100
                ],
                "borderWidth": [
                    1,
                    1,
                    1,
                    1
                ],
                "theme": [
                    "",
                    "",
                    "",
                    ""
                ],
                "options": [
                    "",
                    "",
                    "",
                    ""
                ],
                "chartType": [
                    "",
                    "",
                    "",
                    ""
                ],
                "layoutSpec": [
                    "",
                    "",
                    "",
                    ""
                ],
                "filterSpec": [
                    "",
                    "",
                    "",
                    ""
                ],
                "sortColRefs": [
                    "[]",
                    "",
                    "[]",
                    ""
                ],
                "linkSrcSectionRef": [
                    0,
                    0,
                    0,
                    0
                ],
                "linkSrcColRef": [
                    0,
                    0,
                    0,
                    0
                ],
                "linkTargetColRef": [
                    0,
                    0,
                    0,
                    0
                ],
                "embedId": [
                    "",
                    "",
                    "",
                    ""
                ],
                "rules": [
                    null,
                    null,
                    null,
                    null
                ]
            }
        ],
        "_grist_Views_section_field": [
            "TableData",
            "_grist_Views_section_field",
            [
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12
            ],
            {
                "parentId": [
                    1,
                    1,
                    1,
                    2,
                    2,
                    2,
                    3,
                    3,
                    3,
                    4,
                    4,
                    4
                ],
                "parentPos": [
                    1,
                    2,
                    3,
                    4,
                    5,
                    6,
                    7,
                    8,
                    9,
                    10,
                    11,
                    12
                ],
                "colRef": [
                    2,
                    3,
                    4,
                    2,
                    3,
                    4,
                    6,
                    7,
                    8,
                    6,
                    7,
                    8
                ],
                "width": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "widgetOptions": [
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    ""
                ],
                "displayCol": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "visibleCol": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "filter": [
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    ""
                ],
                "rules": [
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
                ]
            }
        ],
        "_grist_Validations": [
            "TableData",
            "_grist_Validations",
            [],
            {
                "formula": [],
                "name": [],
                "tableRef": []
            }
        ],
        "_grist_REPL_Hist": [
            "TableData",
            "_grist_REPL_Hist",
            [],
            {
                "code": [],
                "outputText": [],
                "errorText": []
            }
        ],
        "_grist_Attachments": [
            "TableData",
            "_grist_Attachments",
            [],
            {
                "fileIdent": [],
                "fileName": [],
                "fileType": [],
                "fileSize": [],
                "imageHeight": [],
                "imageWidth": [],
                "timeDeleted": [],
                "timeUploaded": []
            }
        ],
        "_grist_Triggers": [
            "TableData",
            "_grist_Triggers",
            [],
            {
                "tableRef": [],
                "eventTypes": [],
                "isReadyColRef": [],
                "actions": []
            }
        ],
        "_grist_ACLRules": [
            "TableData",
            "_grist_ACLRules",
            [
                1
            ],
            {
                "resource": [
                    1
                ],
                "permissions": [
                    63
                ],
                "principals": [
                    "[1]"
                ],
                "aclFormula": [
                    ""
                ],
                "aclColumn": [
                    0
                ],
                "aclFormulaParsed": [
                    ""
                ],
                "permissionsText": [
                    ""
                ],
                "rulePos": [
                    null
                ],
                "userAttributes": [
                    ""
                ],
                "memo": [
                    ""
                ]
            }
        ],
        "_grist_ACLResources": [
            "TableData",
            "_grist_ACLResources",
            [
                1
            ],
            {
                "tableId": [
                    ""
                ],
                "colIds": [
                    ""
                ]
            }
        ],
        "_grist_ACLPrincipals": [
            "TableData",
            "_grist_ACLPrincipals",
            [
                1,
                2,
                3,
                4
            ],
            {
                "type": [
                    "group",
                    "group",
                    "group",
                    "group"
                ],
                "userEmail": [
                    "",
                    "",
                    "",
                    ""
                ],
                "userName": [
                    "",
                    "",
                    "",
                    ""
                ],
                "groupName": [
                    "Owners",
                    "Admins",
                    "Editors",
                    "Viewers"
                ],
                "instanceId": [
                    "",
                    "",
                    "",
                    ""
                ]
            }
        ],
        "_grist_ACLMemberships": [
            "TableData",
            "_grist_ACLMemberships",
            [],
            {
                "parent": [],
                "child": []
            }
        ],
        "_grist_Filters": [
            "TableData",
            "_grist_Filters",
            [],
            {
                "viewSectionRef": [],
                "colRef": [],
                "filter": [],
                "pinned": []
            }
        ],
        "_grist_Cells": [
            "TableData",
            "_grist_Cells",
            [],
            {
                "tableRef": [],
                "colRef": [],
                "rowId": [],
                "root": [],
                "parentId": [],
                "type": [],
                "content": [],
                "userRef": []
            }
        ]
    },
    "log": [
        {
            "actionNum": 1,
            "actionHash": "a9c64aa147c516f0ca3c11da5bfcaf4c392b0e04935a7cfda069fd062409a4bc",
            "fromSelf": false,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 2,
            "actionHash": "8d7ea60144c1db2f888176269f29471a6a26d637e438bef4214d788b94fce347",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 3,
            "actionHash": "ccb5dbe4b6120dc1fdcae43827ac98390e315d1244181025e5a7caf6c94870bd",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 4,
            "actionHash": "854943c0d48cce6b6bd39436e7918f7e8dbaeaa628fe650fa394ced015e8b986",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 1,
            "isUndo": false
        },
        {
            "actionNum": 5,
            "actionHash": "e9737cb86fefdd1994fc63f3ef37774d4b2726035c2638199ade8d215642f6bb",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 6,
            "actionHash": "da2c09fcfe25340213841d59672058ac53c737c6483fc69ad74ba022027298c7",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 7,
            "actionHash": "8d377ee2d217e218460875e4956749fab0d90e1011dbac586063279cf49ca27b",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 0,
            "isUndo": false
        },
        {
            "actionNum": 8,
            "actionHash": "5fe4558ff365a835cb94d6e81cb357aaeece0890b0f53efa868e5659ffddf974",
            "fromSelf": true,
            "linkId": 0,
            "otherId": 0,
            "rowIdHint": 1,
            "isUndo": false
        }
    ],
    "recoveryMode": false,
    "docUsage": {
        "dataLimitStatus": null,
        "rowCount": {
            "1": 1,
            "2": 1,
            "total": 2
        },
        "dataSizeBytes": 1277,
        "attachmentsSizeBytes": 0
    }
}

@paulfitz
Copy link
Member

Oh sorry, I misunderstood. I think visibleCol is relevant to what you are looking for:

# Points to a display column, if it exists, for this column.
make_column("displayCol", "Ref:_grist_Tables_column"),
# For Ref cols only, points to the column in the pointed-to table, which is to be displayed.
# E.g. Foo.person may have a visibleCol pointing to People.Name, with the displayCol
# pointing to Foo._gristHelper_DisplayX column with the formula "$person.Name".
make_column("visibleCol", "Ref:_grist_Tables_column"),

@Vinschni
Copy link
Contributor Author

Vinschni commented Mar 21, 2023

Dear @paulfitz, this works.
I'm working with grist data in python.
I wrote a table schema independent function in python that follows all references to all referenced tables in a fetched grist table and replaces the row id in the fetched data with the value in the column and row of the referenced table.

Took more steps than i initially thought.

  
def fetchAndFollowRefsGristTable(grist,tableName):
    """Fetches and follows references in all columns of a grist table and replaces it with the data of the reference table
       RefList reference values are resolved as comma seperated strings

    Args:
        grist (grist_api.grist_api.GristDocAPI): Grist api pbject
        tableName (str): Name of the grist table to fetch

    Returns:
        dict, dict: Original fetched table, table with reference IDs in reference columns replaced with real data
    """
    gristTableOriginal = grist.fetch_table(tableName)

    # Transform into list of dict
    gristTable = [{task._fields[i]: task[i] for i in range(len(task))} for task in gristTableOriginal]

    # Follow references, otherwise we just get an integer for grist columns which are references
    # This process is generic and can handle any number and name of reference columns and tables
    # Get all columns of the table
    gristCols = grist.call(url=f'tables/{tableName}/columns')['columns']
    # Find out which are references and remember the reference table
    gristRefTableName = {col['id']:col['fields']['type'] for col in gristCols if col['fields']['type'].startswith("Ref:") or col['fields']['type'].startswith("RefList:")}

    # Remove reference beginning to directly be able to query reference table
    gristRefTableName = {key:val.lstrip("Ref:").lstrip("List:") for key,val in gristRefTableName.items()}
    gristRefColumns = {key: grist.call(url=f'tables/{val}/records')['records'] for key,val in gristRefTableName.items()}

    # Query column definitions of reference columns
    gristRefTableColumnDefs = {column: grist.call(url=f'tables/{refTable}/columns')['columns'] for column,refTable in gristRefTableName.items()}

    # Match column definitions with the reference columns
    gristRefFieldColumnDefs = {refColName: next((colDef for colDef in gristCols if colDef['id'] == refColName)) for refColName in list(gristRefTableName.keys())}

    # Retreive id of referenced visible column 
    visibleColPerRefCol = {refColName: gristRefFieldColumnDefs[refColName]['fields']['visibleCol'] for refColName in  list(gristRefTableName.keys())}

    # Match the visible column with the column reference id to find the column id for each reference column
    gristRefColumnName = {refColName: [colDef['id'] for colDef in gristRefTableColumnDefs[refColName] if colDef['fields']['colRef'] == visibleColPerRefCol[refColName]][0] for refColName in  list(gristRefTableName.keys())}

    # Replace reference values with name of value for all fields that are reference columns in grist
    for idx, _ in enumerate(gristTable):
        for refField in gristRefColumns.keys():
            if type(gristTable[idx][refField]) == type([]):
                # Dealing with a reference list, resolve all references as string and concat them with comma:
                resolvedReferenceValueList = [sub for sub in gristRefColumns[refField] if sub['id'] in gristTable[idx][refField]]
                if len(resolvedReferenceValueList) > 0:
                    gristTable[idx][refField] = ','.join(val['fields'][gristRefColumnName[refField]] for val in resolvedReferenceValueList)
            elif type(gristTable[idx][refField]) == type(0):
                # Dealing with single reference
                resolvedReferenceValue = next((sub for sub in gristRefColumns[refField] if sub['id'] == gristTable[idx][refField]), None)
                if resolvedReferenceValue != None:
                    gristTable[idx][refField] = resolvedReferenceValue['fields'][gristRefColumnName[refField]]
            else:
                raise Exception("Unknown reference field type, should never happen.")
           
    return gristTableOriginal,gristTable

UPDATE 2023-04-02:

  • Now handling RefList references
  • Fixed handling of columns that have space in name (do use id, not label)

@paulfitz
Copy link
Member

Glad you got it working @Vinschni. It is silly that our /records endpoint doesn't deal with this, given that the download endpoints for csv and xlsx will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants