diff --git a/README.md b/README.md index bc2b4d5c..0e1d76a0 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ Welcome to the SeaTable Developer's Repository! πŸŒŠπŸ”βœ¨ -This repository serves as the foundational source for the SeaTable Developer's Manual available at https://developer.seatable.com. The developer manual is generated with the help of MkDocs Material and is a comprehensive guide and resource hub for developers aiming to build extensions, scripts, plugins, or custom applications within SeaTable. +This repository serves as the foundational source for the SeaTable Developer's Manual available at https://developer.seatable.com. The Developer Manual is generated with the help of MkDocs Material and is a comprehensive guide and resource hub for developers aiming to build extensions, scripts, plugins, or custom applications within SeaTable. ## Content - **Introducion**: Explanation of fundamental approaches and SeaTable basic concepts. - **Scripting in SeaTable**: Detailed instructions on scripting with a complete function overview and ready-to-use scripts. - **Plugin Development**: Step-by-step guide to developing your own SeaTable plugin. -- **Client API's**: List of ready-to-use API clients for various programming languages like JavaScript, Python, and PHP. +- **Client APIs**: List of ready-to-use API clients for various programming languages like JavaScript, Python, and PHP. ## How to participate -Please fell free to particiate in the developer manual by creating pull requests. Before you do this, please test your changes in a local copy of this manual. Here is how you can do this. +Please fell free to participate in the Developer Manual by creating pull requests. Before you do this, please test your changes in a local copy of this manual. Here is how you can do this. > :warning: Docker is required > @@ -30,7 +30,7 @@ git checkout -b # please replace with something short like "add_python_example" ``` -### Step 2: Generate your local version of the developer manual +### Step 2: Generate your local version of the Developer Manual We developed a tiny bash script to generate the local copy of the manual. @@ -52,7 +52,7 @@ git commit -m "" git push ``` -### Step 4: Stop the docker container with your local admin manual copy +### Step 4: Stop the docker container with your local Developer Manual copy ```bash ./preview.sh -stop diff --git a/docs/clients/index.md b/docs/clients/index.md index b138611f..7bde4ec9 100644 --- a/docs/clients/index.md +++ b/docs/clients/index.md @@ -1,7 +1,7 @@ -# Client API's +# Client APIs -Thanks to seatable's full API, virtually anything can be controlled with any programming language. +Thanks to SeaTable's full API, virtually anything can be controlled with any programming language. -On [https://api.seatable.com](https://api.seatable.com) you can find all available API commands and sample commands for different programming languages. +On the [API Reference](https://api.seatable.com) you can find all available API commands and sample codes for different programming languages. For a few programming languages there are already ready-to-use client APIs classes that do some of the work for you. These are presented in this part of the documentation. diff --git a/docs/clients/javascript/columns.md b/docs/clients/javascript/columns.md index 747c6364..6b1ee944 100644 --- a/docs/clients/javascript/columns.md +++ b/docs/clients/javascript/columns.md @@ -121,7 +121,7 @@ Every table in a base contains columns. The following calls are available to int !!! question "addColumnOptions" - Used by single-select or multiple-select type columns + Used by "single select" or "multiple select"-type columns ``` js base.addColumnOptions(table_name, column, options) ``` @@ -137,7 +137,7 @@ Every table in a base contains columns. The following calls are available to int !!! question "addColumnCascadeSettings" - Used by single-select column, to add a limitation of child column options according to the option of parent column + Used by "single select"-type column, to add a limitation of child column options according to the option of parent column ``` js base.addColumnCascadeSettings(table_name, child_column, parent_column, cascade_settings) ``` diff --git a/docs/clients/javascript/javascript_api.md b/docs/clients/javascript/javascript_api.md index 76b21818..c4aa562b 100644 --- a/docs/clients/javascript/javascript_api.md +++ b/docs/clients/javascript/javascript_api.md @@ -1,10 +1,11 @@ -# JavaScript Client +# JavaScript client -The SeaTable JavaScript Client encapsulates SeaTable Server Restful API. You can call it in your front-end page or Node.js program. +The SeaTable JavaScript client encapsulates SeaTable Server Restful API. You can call it in your front-end page or Node.js program. -!!! danger "JavaScript API cannot be used for scripts in SeaTable bases. For script programming with Javascript, there is a [separate chapter](/scripts/) in this documentation." +!!! warning "Two different clients" + JavaScript API cannot be used for scripts in SeaTable bases. For script programming with JavaScript, there is a [separate chapter](../../scripts/javascript/objects/index.md) in this documentation. -Note, JavaScript API calls SeaTable Server Restful API, while scripts in SeaTable bases interact with the base loaded in the browser, so the APIs of the two are somewhat different. +Note that JavaScript API calls SeaTable Server Restful API, whereas scripts in SeaTable bases interact with the base loaded in the browser, so the APIs of the two are somewhat different. ## Installation @@ -16,45 +17,43 @@ The source code of the JavaScript Client API is available at [GitHub](https://gi ## Reference -To use SeaTable APIs, you should first initialize a base object and call `base.auth()`. `base.auth()` is an async function, which needs to be executed in async functions. Other APIs all return a promise object. There are two ways to use them +To use SeaTable APIs, you should first initialize a base object and call `base.auth()`. `base.auth()` is an async function, which needs to be executed in async functions. Other APIs all return a promise object. There are two ways to use them: -The first way: +=== "First way using then" -``` -base.listViews(tableName).then(views => { - // Use views to complete the requirements -}).catch(error => { - // Exception handling -}) -``` + ```js + base.listViews(tableName).then(views => { + // Use views to complete the requirements + }).catch(error => { + // Exception handling + }) + ``` -The second way: +=== "Second way using await" -``` -try { - const views = await base.listViews(tableName); - // Use views to complete the requirements -} catch (error) { - // Exception handling -} -``` + ```js + try { + const views = await base.listViews(tableName); + // Use views to complete the requirements + } catch (error) { + // Exception handling + } + ``` -SeaTable API Errors +Here are the main SeaTable API errors you might encounter: - 400 Params invalid - 403 Permission denied -- 413 exceed limit +- 413 Exceed limit (see the [API Reference](https://api.seatable.com/reference/limits) about limits) - 500 Internal Server Error ## Authorization -Base represents a table. You can use the api token of the form to obtain the authorization to read and write the base. This token can be generated directly on the web side. - -Use the API Token of the base to get access authorization. +The `Base` object represents a table. You need to specify an `APIToken` to get access authorization and to be able to read and write the base. API tokens can be directly [generated in the web interface](https://seatable.com/help/erzeugen-eines-api-tokens/). -##### Example +__Example__ -```javascript +```js import { Base } from "seatable-api"; const config = { diff --git a/docs/clients/php_api.md b/docs/clients/php_api.md index fa9ad438..0e778c96 100644 --- a/docs/clients/php_api.md +++ b/docs/clients/php_api.md @@ -1,8 +1,8 @@ -# PHP Client +# PHP client SeaTable's API exposes the entire SeaTable features via a standardized programmatic interface. The _SeaTable PHP Client_ encapsulates SeaTable Server Restful API. If you are familiar this client enables you to call every available API endpoint of SeaTable. You can interact with the user accounts, bases or files. -!!! success "Auto generated from openapi specification" +!!! info "Auto generated from openapi specification" Since April 2024, we auto generate this SeaTable php client from our public available openapi specification. The advantage is that, the php client automatically contains all available API endpoints and we save a lot of programming capacity. Also we could generate more api clients for other programming languages in no time with the same feature set. The disadvantage is, that with this new client we removed some convenitent functions for authentication and the new version is not compatible at all with the version v0.2 and earlier. @@ -16,7 +16,7 @@ composer require seatable/seatable-api-php The source code of the PHP Client API is available at [GitHub](https://github.com/seatable/seatable-api-php). -## Getting Started +## Getting started After installation you can easily connect to your SeaTable system and execute API calls. diff --git a/docs/clients/python_api.md b/docs/clients/python_api.md index e2fee4da..68c223e6 100644 --- a/docs/clients/python_api.md +++ b/docs/clients/python_api.md @@ -1,8 +1,9 @@ -# Python Client +# Python client -The SeaTable Python Client encapsulates SeaTable Server Restful API. You can call it in your python programm. +The SeaTable Python Client encapsulates SeaTable Server Restful API. You can call it in your python program. -!!! success "External python programms and python scripts, executed in SeaTable, use the same python library and therefore share the same functions. For an overview of the available functions, read the chapter of [script programming with Python](/scripts/python/basic_structure_python/) in this documentation." +!!! info "Unique Python library" + Unlike JavaScript, external python programs and python scripts, executed in SeaTable, use the same python library and therefore share the same functions. For an overview of the available functions, read the chapter of [script programming with Python](../scripts/python/introduction.md) in this documentation." ## Installation diff --git a/docs/clients/ruby_api.md b/docs/clients/ruby_api.md index adf7c9b8..afe59c45 100644 --- a/docs/clients/ruby_api.md +++ b/docs/clients/ruby_api.md @@ -1,5 +1,5 @@ -# Ruby Client +# Ruby client -One of our community members [made a first version](https://forum.seatable.com/t/seatable-ruby-ruby-gem-for-seatable/2366) of a SeaTable Ruby Client. +One of our community members [made a first version](https://forum.seatable.com/t/seatable-ruby-ruby-gem-for-seatable/2366) of a SeaTable Ruby client. -The source code of the Ruby Client API and additional explanations are available at [GitHub](https://github.com/viktorMarkevich/seatable_ruby). +The source code of the Ruby client API and additional explanations are available at [GitHub](https://github.com/viktorMarkevich/seatable_ruby). diff --git a/docs/includes.md b/docs/includes.md new file mode 100644 index 00000000..264e2c18 --- /dev/null +++ b/docs/includes.md @@ -0,0 +1,491 @@ + +As a developer you typically interact with a single base. In SeaTable, a base can contain multiple tables, each one containing multiple rows and columns (or fields) and eventually multiple views used to filter, sort and/or group these rows and columns. The logic is like this: + +```sh +SeaTable Base +β”œβ”€ Table 1 (Column A | Column B | Column C) +β”‚ └─ View A (Column A | Column B | Column C) +| └─ Row 1 +| └─ Row 2 +| └─ Row 3 +| └─ ... +β”‚ └─ View B (Column A | Column C) +| └─ Row 3 +| └─ Row 4 +└─ Table 2 +| └─ ... +``` + +Every objects and methods will help you interact with this architecture. For details about the different objects (tables, view, rows & columns and links) you can look at the global structure presented in each object page or at the [SeaTable API Reference](https://api.seatable.com/reference/models) for even more information. + + + +## Global structure + +Here is the global structure of a table object: +```js +{ + "_id": "IfcB", + "name": "New table", + "is_header_locked": false, + "summary_configs": {}, + "columns": [ // (1)! + { + "key": "0000", + "type": "number", + "name": "First column", + "editable": true, + "width": 200, + "resizable": true, + "draggable": true, + "data": null, + "permission_type": "", + "permitted_users": [] + }, + { + "key": "2w6F", + "type": "text", + "name": "second column", + "editable": true, + "width": 200, + "resizable": true, + "draggable": true, + "data": null, + "permission_type": "", + "permitted_users": [] + }, + { + "key": "3aAf", + "type": "date", + "name": "third column", + "editable": true, + "width": 200, + "resizable": true, + "draggable": true, + "data": null, + "permission_type": "", + "permitted_users": [] + } + ], + "rows": [], // (2)! + "views": [ // (3)! + { + "_id": "0000", + "name": "Default View", + "type": "table", + "is_locked": false, + "filter_conjunction": "And", + "filters": [], + "sorts": [], + "groupbys": [], + "group_rows": [], + "groups": [], + "colorbys": {}, + "hidden_columns": [], + "rows": [], + "formula_rows": {}, + "link_rows": {}, + "summaries": {}, + "colors": {} + } + ], + "id_row_map": {} +} +``` + +1. Array of existing columns + ```js + { + "key": "g4s1", + "type": "number", + "name": "api3", + "editable": true, + "width": 200, + "resizable": true, + "draggable": true, + "data": null, + "permission_type": "", + "permitted_users": [] + } + ``` + +2. Array of existing rows + ```js + { + "_id": "Qtf7xPmoRaiFyQPO1aENTjb", + "_mtime": "2021-03-10T16:19:31.761+00:00", + "Name": "NewName", + "Date": "2020-08-01", + "Content": "111", + "link": [ + { + "display_value": "1", + "row_id": "XzdZfL2oS-aILnhfagTWEg" + } + ] + } + ``` + +3. Array of existing views + ```js + { + "_id": "0000", + "name": "Default View", + "type": "table", + "is_locked": false, + "rows": [], + "formula_rows": {}, + "summaries": [], + "filter_conjunction": "And", + "sorts": [], + "filters": [], + "hidden_columns": [], + "groupbys": [], + "group_rows": [], + "groups": [] + } + ``` +Please refer to the [SeaTable API Reference](https://api.seatable.com/reference/models#table) for a more detailed presentation. + + + + +## Global structure + +Here is the global structure of a view object: + +```js +{ + "_id": "0000", + "name": "Default View", + "type": "table", + "is_locked": false, + "rows": [], + "formula_rows": {}, + "summaries": [], + "filter_conjunction": "And", + "sorts": [], + "filters": [], + "hidden_columns": [], + "groupbys": [], + "group_rows": [], + "groups": [] +} +``` + +Please refer to the [SeaTable API Reference](https://api.seatable.com/reference/models#view) for a more detailed presentation. + + + +## Global structure + +Here is the global structure of a column object: + +```js +{ + "key":"bjcM", + "type":"number", + "name":"Val", + "editable":true, + "width":200, + "resizable":true, + "draggable":true, + "data": // (1)! + { + "format":"number", + "precision":2, + "enable_precision":false, + "enable_fill_default_value":false, + "enable_check_format":false, + "decimal":"comma", + "thousands":"no", + "format_min_value":0, + "format_max_value":1000 + }, + "permission_type":"", + "permitted_users":[], + "permitted_group":[], + "edit_metadata_permission_type":"", + "edit_metadata_permitted_users":[], + "edit_metadata_permitted_group":[], + "description":null, + "colorbys":{}, + "editor": + { + "key":null, + "ref":null, + "props":{}, + "_owner":null + }, + "formatter": + { + "key":null, + "ref":null, + "props":{}, + "_owner":null + } +} +``` + +1. See below for a presentation of `data` object keys depending on the column `type` + +!!! warning "Columns particularities" + + - Unless other elements, columns don't have an `_id`, but a `key` + - Link-type columns also have a link id that should not be mistaken with the column `key`. This value is present in the `data` object (see below) + +### Column data + +The `data` object keys will depend on the column `type` and will allow you to define the specific column parameters. Here is a list of the different `data` keys depending on the column `type`: + +!!! note "`text`, `email`, `long-text`, `image`, `file`, `url`, `creator`, `ctime`, `last-modifier`, `mtime`" + + empty + +??? note "`link`" + + ``` + { + "display_column_key":"qqXZ", + "table_id":"0000", + "other_table_id":"XE5U", + "is_internal_link":true, + "is_multiple":true, + "only_adding_new_record":false, + "is_row_from_view":false, + "other_view_id":"", + "link_id":"OSD1", + "array_type":"text", + "array_data":null, + "result_type":"array" + } + ``` + +??? note "`number`" + + ``` + { + "format":"custom_currency", + "precision":2, + "enable_precision":true, + "enable_fill_default_value":false, + "decimal":"comma", + "thousands":"no", + "currency_symbol_position":"after", + "currency_symbol":"p" + } + ``` + +??? note "`date`" + + ``` + { + "format":"M/D/YYYY HH:mm" + } + ``` + +??? note "`duration`" + + ``` + { + "format":"duration", + "duration_format":"h:mm" + } + ``` + +??? note "`single select, multiple select`" + + ``` + { + "options": + [ + { + "name":"Male", + "id":"783482", + "color":"#46A1FD", + "textColor":"#FFFFFF", + "borderColor":"#3C8FE4" + }, + { + "name":"Female", + "id":"330935", + "color":"#DC82D2", + "textColor":"#FFFFFF", + "borderColor":"#D166C5" + }, + { + "name":"Non-binary", + "id":"147140", + "color":"#ADDF84", + "textColor":"#FFFFFF", + "borderColor":"#9CCF72" + } + ], + "cascade_column_key":"Qvkt", + "cascade_settings": + { + "147140":["783482"], + "330935":["330935"], + "783482":["783482"] + } + } + ``` + +??? note "`checkbox`" + + ``` + { + "default_value":false, + "enable_fill_default_value":false + } + ``` + +??? note "`rate`" + + ``` + { + "rate_max_number":5, + "rate_style_color":"#FF8000", + "default_value":"", + "enable_fill_default_value":false + } + ``` + +??? note "`formula`" + + ``` + { + "formula":"left({Email},search(\"@\",{Email},1)-1)", + "operated_columns":["JfP2"], + "result_type":"string", + "enable_precision":true, + "precision":1, + "thousands":"no" + } + ``` + +??? note "`link-formula`" + + ``` + { + "formula":"findmax", + "result_type":"array", + "operated_columns":["TaXD"], + "conditions":[], + "link_column_key":"TaXD", + "include_condition":false, + "condition_conjunction":"And", + "column_key_in_linked_record":"0000", + "column_key_for_comparison":"RSjx", + "level2_linked_table_column_key":null, + "array_type":"auto-number", + "array_data":null + } + ``` + +??? note "`geolocation`" + + ``` + { + "geo_format":"lng_lat" + } + ``` + +??? note "`auto-number`" + + ``` + { + "format":"YYYYMMDD-00", + "max_used_auto_number":33, + "digits":2, + "prefix_type":"date", + "prefix":"20250909" + } + ``` + +??? note "`button`" + + ``` + { + "button_type":"copy_row_to_another_table", + "button_name":"Copy to Table2", + "button_color":"#FFFCB5", + "table_id":"0000" + } + ``` + +!!! info "Accessing a particular data object value" + + This rather long list is not exhaustive, however. If you need to access a specific `data` value, consult the [SeaTable API Reference](https://api.seatable.com/reference/models#row--column) or create the corresponding column to display the content of its `data` object. + + + + +## Global structure + +Here is the global structure of a row object: +```js +{ + "_id": "Qtf7xPmoRaiFyQPO1aENTjb", + "_mtime": "2021-03-10T16:19:31.761+00:00", + "Name": "NewName", + "Date": "2020-08-01", + "Content": "111", + "link": [ + { + "display_value": "1", + "row_id": "XzdZfL2oS-aILnhfagTWEg" + } + ] +} +``` + +Please note the specific format for link-type columns (structure of the array objects for key `link`): + +- `display_value`: Value displayed in the cell + +- `row_id`: id of the linked row in the other table + + + + +# Add rows + +This script adds two expenses rows in a ledger. Before adding them, it checks if they have already been added for the current month. + +Here is the structure of the table named `Daily expenses` you need so that this script could run: + +| Column name | Name | Date | Type | Type (single select) | Fee | +| ----------- |: ------ :|: ------ :|: ------ :|: ---------------------- :|: ----- :| +| **Column type** | text | date | text | single select | number | + + + +# Calculate accumulated value + +This script accumulates the values of the current row and the previous rows, and records the result to the current row. It does the same than the *Calculate accumulated value* operation from the data processing menu. If there's a grouping rule active on the view, accumulated values will be calculated for each group.Otherwise, values are accumulated for all rows. Please not that this script only supports **grouping by a single column**. + + Here is the structure of the table named `Accumulated value` you need so that this script could run: + +| Column name | Value to add | Incremental total | Grouping column | +| ----------- |: ------ :|: ------ :|: ------ :| +| **Column type** | number | number | single select | + + + +# Compute attendance statistics + +This script computes, from a list of clocking times, daily clock in (earliest clocking) and clock out (latest clocking) times for each day and staff member. + +Here is the structure of the table named `Clocking table` that contains the input data: + +| Column name | Name | Department | Date | Clocking time | +| ----------- |: ------ :|: ------ :|: ------ :|: ------ :| +| **Column type** | text | single select | date | duration | + +And the structure of the table `Attendance statistics` where the daily summarized values will be stored: + +| Column name | Name | Department | Date | Clock-in | Clock-out | +| ----------- |: ------ :|: ------ :|: ------ :|: ------ :|: ------ :| +| **Column type** | text | single select | date | duration | duration | + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 6ea2b9f3..b63820db 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,36 +1,76 @@ # Introduction -Welcome to the SeaTable Developer Manual, your comprehensive resource for leveraging the potential of SeaTable, the world's leading self-hosted no-code platform. +You've decided to venture into developing your own script, plugin, or custom application: excellent choice! This guide is designed to cover all aspects of this journey. While some descriptions might seem obvious to seasoned professionals, this manual is crafted to assist novice developers who are just starting out. + +## Who is this manual for? + +The Developer Manual caters to **developers** interested in utilizing custom scripts within SeaTable, creating their own plugins, or developing custom programs. Both minimal programming skills and knowledge of SeaTable are therefore recommended to take full advantage of this manual. + +!!! info "Tips for beginners" + + You don't feel familiar enough with coding or with SeaTable? Depending on your actual skills, knowledge and aims, here are some suggested starting points: + + - You would like to get started but currently have no programming knowledge? We invite you to consult the [Coding for beginners page](/introduction/coding_for_beginnners) + + - You are new to SeaTable? Do not hesitate to consult [SeaTable's user manual](https://seatable.com/help/) to get more familiar with it. + +## Scope of this manual This guide illustrates **three fundamental approaches** to development within SeaTable: 1. [Scripting within SeaTable](/scripts/): Create custom logic or perform individual data processing using JavaScript or Python, both supported within SeaTable. -1. [SeaTable plugins](/plugins/): Develop plugins capable of interacting with, visualizing, and operating on data within a SeaTable Base. -1. [Utilizing any programming language with SeaTable's API](/clients/): Seamlessly interact with the SeaTable API to construct your own web pages or programs. +2. [SeaTable plugins](/plugins/): Develop plugins capable of interacting with, visualizing, and operating on data within a SeaTable Base. +3. [Utilizing any programming language with SeaTable's API](/clients/): Seamlessly interact with the SeaTable API to construct your own web pages or programs. -## Developer decision tree +!!! info "JavaScript or Python scripts?" -![Image title](/media/developer_decision_tree.png){ align=left } + Differences between JavaScript and Python (in terms of abilities and requirements) are mentioned in the [Scripting introduction page](./scripts/index.md) to help you make the right choice depending on your needs + +All instructions provided are applicable to self-hosted SeaTable installations (Enterprise and Developer Editions), as well as to SeaTable Cloud. + +### Where to start? -For guidance on choosing the right section within this manual, refer to the decision tree diagram above. +For guidance on choosing the right section within this manual, refer to the decision tree diagram below. + +![Image title](/media/developer_decision_tree.png){ align=left } If you aim to integrate a software product with SeaTable, note that SeaTable supports multiple workflow automation tools such as [n8n](https://n8n.io/integrations/seatable/), [Zapier](https://zapier.com/apps/seatable/integrations), and [Make](https://www.make.com/en/integrations/seatable). Please refer to the [SeaTable User Manual](https://seatable.com/help/integrations/) for detailed information on these integrations, as they are not covered here. -## Target audience +## Requirements -The Developer Manual caters to **developers** interested in utilizing custom scripts within SeaTable, creating their own plugins, or developing custom programs. +### Development system -All instructions provided are applicable to self-hosted SeaTable installations (Enterprise and Developer Editions), as well as to SeaTable Cloud. +To begin your development journey with SeaTable, you'll need a SeaTable system. If you're planning to create short scripts, [SeaTable Cloud](https://seatable.com/prices/) could be a suitable option. However, for more in-depth development or when creating plugins, it's highly recommended to set up your own SeaTable Server. Refer to the [Admin manual](https://admin.seatable.com) for installation instructions. !!! warning annotate "Known limitations of SeaTable Cloud" 1. **Custom Plugin Installation**: [SeaTable Cloud](https://cloud.seatable.io) does not support the installation of custom plugins. 2. **Python Script Runs Limitation**: The number of Python script runs is constrained by your current SeaTable Cloud subscription. - Therefore, it's recommended to set up your own SeaTable Server if you intend to develop custom plugins, applications, or run multiple Python scripts. For further information about deploying your server, please refer to the [Admin manual](https://admin.seatable.com). + Therefore, it's recommended to set up your own SeaTable Server if you intend to develop custom plugins, applications, or run multiple Python scripts. For further information about deploying your server, please refer to the [Admin Manual](https://admin.seatable.com). + +### Authentication + +The actual authentication depends on the development approach one chooses. + +=== "Scripts" + + JavaScript Scripts does not require any authentication at all because these scripts are executed in the browser of the user and the user has to be authenticated already. + + Plugin Scripts require an authentication to get data from the base, but the `context` objects contains everything for an easy authentication. + +=== "Plugins" + + Plugins interact with the data of one base. SeaTable provides all required functions for easy authentication. + +=== "Client APIs" + + If you want to build your own application you always have to authenticate with a base token against the base (learn more about the different tokens used by SeaTable in the [API Reference](https://api.seatable.com/reference/authentication)). -If you are new to SeaTable, we suggest starting with the introduction section covering the platform's [requirements](/introduction/requirements) and [basic concepts](/introduction/basic_concepts) of this no-code platform. Otherwise, let's dive right in! +## Data model -[Start scripting](/scripts){ .md-button .md-button--primary } -[Write your own plugin](/plugins){ .md-button .md-button--primary } -[Use the API](/clients){ .md-button .md-button--primary } +{% + include-markdown "includes.md" + start="" + end="" +%} \ No newline at end of file diff --git a/docs/introduction/basic_concepts.md b/docs/introduction/basic_concepts.md deleted file mode 100644 index 9c528a5a..00000000 --- a/docs/introduction/basic_concepts.md +++ /dev/null @@ -1,127 +0,0 @@ -# Basic concepts - -SeaTable is the world leading self-hosted no-code platform. With seatable, you can digitize processes and workflows in the shortest possible time without having to write a line of code. - -Even though you don't need any programming skills to use SeaTable, the digital Lego construction kit for developers offers various interfaces and automation options. - -## Right solution for your purpose - -Depending on what you want to do with seatable, this manual is divided into three major sections. This manual explains how you can build such solutions by yourself. - -Here are **three examples**, one for each section of this documentation: - -### Python script to get the structure of a base - -You can take the following python code and copy&paste it to SeaTable. It will return the complete metastructure of your base. Easy or not? - -=== "Python code" - - ```python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() - - metadata = base.get_metadata() - - print("--- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS ---") - for table in metadata['tables']: - print('.') - print("Table: "+table['name']+" (ID: "+table['_id']+")") - for column in table['columns']: - link_target = "" - if column['type'] == "link": - link_target = " --> "+column['data']['other_table_id'] - if column['data']['other_table_id'] == table['_id']: - link_target = " --> "+column['data']['table_id'] - print(" --> "+column['name']+" ("+column['type']+link_target+")") - ``` - -=== "Output" - - ``` - --- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS --- - . - Table: Opportunities (ID: 9g8f) - --> Name (text) - --> Status (single-select) - --> Prio (single-select) - --> Owner (collaborator) - --> Customer (link --> deGa) - --> Estimated value (number) - --> Proposal deadline (date) - --> Contacts (link --> lYb8) - --> Interactions (link --> 0000) - . - Table: Interactions (ID: 0000) - --> Interaction ID (auto-number) - --> Opportunity (link --> 9g8f) - --> Type (single-select) - --> Interaction (formula) - --> Opportunity status (formula) - --> Date and time (date) - --> Contact (link --> lYb8) - --> Notes (long-text)# - ``` - -### Gallery-Plugin - -SeaTable provides some Plugins to visualize your data. Examples for such a plugin are the [Gallery](https://seatable.com/help/anleitung-zum-galerie-plugin/), [Timeline](https://seatable.com/help/anleitung-zum-timeline-plugin/), [Kanban](https://seatable.com/help/anleitung-zum-kanban-plugin/) and so on. But SeaTable has everything that you build your own plugin. There are no limits to the imagination, it just requires some time and React skills. - -![Screenshot of the Galery Plugin](/media/gallery.png) - -### Custom app: SeaTable ideas - -There are multiple API classes available for various programming languages. This enables you to build any app or website you want. - -Our feature request tool [SeaTable Ideas](https://ideas.seatable.com) is an example for such a website. It uses SeaTable as database and the frontend is build completely with PHP and the [slim framework](https://www.slimframework.com/). - -![Screenshot of ideas.seatable.com](/media/ideas.png). - -## Data model - -As a developer you typically interact with a single base. In SeaTable, a base can contain multiple tables, and each table contains multiple rows and columns. A row contains multiple fields. - -The logic is like this: - -```sh -SeaTable Base -β”œβ”€ Table 1 -β”‚ └─ View A -| └─ Row 1 -| └─ Row 2 -| └─ Row 3 -β”‚ └─ View B -| └─ Row 3 -| └─ Row 4 -└─ Table 2 -| └─ ... -``` - -SeaTable offers a visual interface, which can be operated with the browser. - -![Screenshot of a SeaTable base](/media/elements_seatable_base.png) - -Look at the [SeaTable API Reference](https://api.seatable.com/reference/models) for more details about the different objects in SeaTable like: - -- Table -- View -- Row & column -- Link - -## Authentication - -The actual authentication depends on the development approach one chooses. - -=== "Scripts" - - Javascript Scripts does not require any authentication at all because these scripts are executed in the browser of the user and the user has to be authenticated already. - - Plugin Scripts require an authentication to get data from the base, but the `context` objects contains everything for an easy authentication. - -=== "Plugins" - - Plugins interact with the data of one base. SeaTable provides all required functions for easy authentication. - -=== "Client APIs" - - If you want to build your own application you always have to authenticate with a base token against the base. diff --git a/docs/introduction/coding_for_beginners.md b/docs/introduction/coding_for_beginners.md new file mode 100644 index 00000000..4314b761 --- /dev/null +++ b/docs/introduction/coding_for_beginners.md @@ -0,0 +1,154 @@ +# Coding for beginners + +## What to learn? + +The Developer Manual is divided into three major sections ([scripts](../scripts/index.md), [plugins](../plugins/index.md), or [API client](../clients/index.md)) depending on what you want to do with SeaTable. Your development requirements will naturally vary based on your intended project. Below is an outline of the skills you might need: + +=== "Scripts" + + Scripts inside SeaTable can only be written with either JavaScript or Python. Therefore you will only require one of these programming languages. + +=== "Plugins" + + The development of a custom plugin for your own SeaTable Server requires profound knowledge of JavaScript and React. + + Even if the `SeaTable plugin templates` offers some reusable components, you will need some experience with React to build the interface of your plugin. + +=== "Client API's" + + Due to the publicly available and well documented API documentation, you can theoretically interact with SeaTable using any programming language. + +## Learn the fundamentals + +If you're relatively new to development, diving into general tutorials can lay a strong foundation for your SeaTable development journey. + +While numerous free online tutorials cover various programming languages, investing in a comprehensive online course or a well-structured book can be invaluable. While free resources are available, a structured course or book often offers a more cohesive and thorough learning experience. + +These paid resources, though requiring a small investment, often provide: + +- **Structured Learning**: A step-by-step approach ensuring a coherent understanding. +- **Comprehensive Content**: In-depth coverage of essential concepts and practical applications. +- **Consistency**: Ensuring continuity and coherence in learning. + +Remember, while free tutorials are abundant, investing in a structured resource can significantly expedite your learning process and provide a solid understanding of programming fundamentals essential for SeaTable development. + +!!! info "This are personal recommendations" + + The following sources does not contain any affiliate links and we do not earn any money from these recommendations. These are just good sources that we have used ourselves in the past. + +=== "JavaScript" + + **Free online course** + + : A solid and __free online__ course is available from codecademy.com. The course [Learn JavaScript](https://www.codecademy.com/learn/introduction-to-javascript) requires a registration but is free and teaches you in approx. 20 hours all necessary skills. + + **Best online course** + + : The best __online course__ on javascript comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with JavaScript in his course [The Ultimate JavaScript Series](https://codewithmosh.com/p/ultimate-javascript-series). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. + + **Book for Beginners** + + : If you prefer a __book__, then we can recommend [JavaScript from Beginner to Professional](https://www.amazon.de/JavaScript-Beginner-Professional-building-interactive/dp/1800562527/). It gives you all the basics for your first steps with JavaScript. + +=== "Python" + + **Free online course** + + : An easy to follow beginner guide comes from Google. At [https://developers.google.com/edu/python](https://developers.google.com/edu/python) you can find this well balanced course to learn how to do your first steps. + + **Best online course** + + : The best __online course__ on Python comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with Python in his course [Complete Python Mastery](https://codewithmosh.com/p/python-programming-course-beginners). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. + + **Book for Beginners** + + : Our recommended book for beginners is called [Learn Python in One Day and Learn It Well](https://www.amazon.de/Python-2nd-Beginners-Hands-Project-ebook/dp/B071Z2Q6TQ) and as far as we can tell it keeps his promise. Most of our working students have read this book if they want to learn more about Python. + +=== "React" + + **Free online course** + + : This free online course comes to you from [Scrimba](https://scrimba.com/). Scrimba is a coding bootcamp with mainly paid courses and a high amount of interactive screencasts. The React course [Learn React](https://scrimba.com/learn/learnreact) is fortunately free of charge. + + **Best online course** + + : The best __online course__ on React comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh will guide and teach you React until and will build a complete Video Game Discovery App. The course is called [React Course for Beginners](https://codewithmosh.com/p/ultimate-react-part1). + +## Learning by doing + +Some of us are more comfortable with learning by doing. The principle is simple: dissect a working example, understand it, and finally modify it so that it achieves what we want. + +Here are **three examples**, one for each approach described in this manual: + +### Python script to get the structure of a base + +You can take the following python code and copy&paste it to SeaTable. It will return the complete metastructure of your base. Easy or not? If you need some more information about this script, please refer to [this step-by-step presentation](https://seatable.com/help/python-beispiel-die-metastruktur-einer-base-auslesen/). + +=== "Python code" + + ```python + from seatable_api import Base, context + base = Base(context.api_token, context.server_url) + base.auth() + + metadata = base.get_metadata() + + print("--- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS ---") + for table in metadata['tables']: + print('.') + print("Table: "+table['name']+" (ID: "+table['_id']+")") + for column in table['columns']: + link_target = "" + if column['type'] == "link": + link_target = " --> "+column['data']['other_table_id'] + if column['data']['other_table_id'] == table['_id']: + link_target = " --> "+column['data']['table_id'] + print(" --> "+column['name']+" ("+column['type']+link_target+")") + ``` + +=== "Output example" + + ``` + --- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS --- + . + Table: Opportunities (ID: 9g8f) + --> Name (text) + --> Status (single-select) + --> Prio (single-select) + --> Owner (collaborator) + --> Customer (link --> deGa) + --> Estimated value (number) + --> Proposal deadline (date) + --> Contacts (link --> lYb8) + --> Interactions (link --> 0000) + . + Table: Interactions (ID: 0000) + --> Interaction ID (auto-number) + --> Opportunity (link --> 9g8f) + --> Type (single-select) + --> Interaction (formula) + --> Opportunity status (formula) + --> Date and time (date) + --> Contact (link --> lYb8) + --> Notes (long-text)# + ``` + +Feel free to check the other [JavaScript](../scripts/javascript/examples/index.md) and [Python](../scripts/python/examples/index.md) examples. + +### Existing plugins + +SeaTable provides some plugins to visualize your data, for example the [Gallery](https://seatable.com/help/anleitung-zum-galerie-plugin/), [Timeline](https://seatable.com/help/anleitung-zum-timeline-plugin/), [Kanban](https://seatable.com/help/anleitung-zum-kanban-plugin/) and so on, but it also offers everything you need to you build your own plugin. There are no limits to the imagination, it just requires some time and React skills. + +For each existing plugin, you can find a corresponding [Github repository](https://github.com/orgs/seatable/repositories?q=seatable-plugin) that will allow you to fork/clone the code and try by yourself (you will probably need some basic git skills too). Please note that this is probably the one of the three approaches ([scripts](../scripts/index.md), [plugins](../plugins/index.md), or [API client](../clients/index.md)) that **requires the most skills**. + +### Using SeaTable APIs: the SeaTable ideas custom app example + +There are multiple API classes available for various programming languages. This enables you to build any app or website you want. + +Our feature request tool [SeaTable Ideas](https://ideas.seatable.com) is an example for such a website. It uses SeaTable as database and the frontend is build completely with PHP and the [slim framework](https://www.slimframework.com/). + +![Screenshot of ideas.seatable.com](/media/ideas.png). + +Do not hesitate to consult this [pretty detailed article](https://seatable.com/seatable-app-frontend-php/) about the logic behind this app. + +Of course, the [SeaTable API Reference](https://api.seatable.com) is another good place to start as it allows you to experiment with most queries, see the responses, and get the corresponding source code for all supported languages. \ No newline at end of file diff --git a/docs/introduction/get_support.md b/docs/introduction/get_support.md index ff3910f8..122e0559 100644 --- a/docs/introduction/get_support.md +++ b/docs/introduction/get_support.md @@ -1,16 +1,16 @@ # Get support -Next to this developer guide there are more documentations available. To learn more about the SeaTable API, the installation of your own server or the usage or SeaTable, please refer to their respective manuals: +Next to this developer guide there are more documentations available. To learn more about the usage or SeaTable, the installation of your own server or the SeaTable API, please refer to their respective manuals: -- [SeaTable User Manual](https://help.seatable.com) -- [SeaTable Admin Manual](https://admin.seatable.com) -- [SeaTable API Reference](https://api.seatable.com) +- [SeaTable User Manual](https://help.seatable.com) detailing how to use SeaTable (with a special section about [scripts](https://seatable.com/help/scripts/)) +- [SeaTable Admin Manual](https://admin.seatable.com) covering all relevant admin topics, from installation, configuration, upgrade, and maintenance +- [SeaTable API Reference](https://api.seatable.com) containing everything you need to know to use SeaTable's API See the [official SeaTable channel](https://youtube.com/seatable) on YouTube for tutorials, guides and overviews. Visit [our blog](https://seatable.com/blog/) for latest news and to learn more about what is going on in and around SeaTable. At any time you could have a look at the SeaTable [Community Forum](https://forum.seatable.com) to share your experience with other users or report issues or bugs. -!!! note "Enterprise support" +!!! info "Enterprise support" If you're using SeaTable in your organization and need assistance, e.g., to __digitalization of processes__, __develop custom solutions__ or __improve efficiency__, diff --git a/docs/introduction/requirements.md b/docs/introduction/requirements.md deleted file mode 100644 index 176c0b9c..00000000 --- a/docs/introduction/requirements.md +++ /dev/null @@ -1,81 +0,0 @@ -# Requirements - -You've decided to venture into developing your own script, plugin, or custom applicationβ€”excellent choice! This guide is designed to cover all aspects of this journey. While some descriptions might seem obvious to seasoned professionals, this manual is crafted to assist novice developers who are just starting out. - -## Development system - -To begin your development journey with SeaTable, you'll need a SeaTable system. If you're planning to create short scripts, [SeaTable Cloud](https://seatable.com/prices/) could be a suitable option. However, for more in-depth development or when creating plugins, it's highly recommended to set up your own SeaTable Server. Refer to the [Admin manual](https://admin.seatable.com) for installation instructions. - -## Programming skills - -Your development requirements will vary based on your intended project. Below is an outline of the skills you might need: - -=== "Scripts" - - Scripts inside SeaTable can only be written with either Javascript or Python. Therefore you will only require one of these programming languages. - -=== "Plugins" - - The development of a custom plugin for your own SeaTable Server requires profound knowlegde of Javascript and react. - - Even if the `SeaTable plugin templates` offers some reusable components, you will need some experience with react to build the interface of your plugin. - -=== "Client API's" - - Due to the publicly available and well documented API documentation, you can theoretically interact with SeaTable using any programming language. - -## Learn the fundamentals - -If you're relatively new to development, diving into general tutorials can lay a strong foundation for your SeaTable development journey. - -While numerous free online tutorials cover various programming languages, investing in a comprehensive online course or a well-structured book can be invaluable. While free resources are available, a structured course or book often offers a more cohesive and thorough learning experience. - -These paid resources, though requiring a small investment, often provide: - -- **Structured Learning**: A step-by-step approach ensuring a coherent understanding. -- **Comprehensive Content**: In-depth coverage of essential concepts and practical applications. -- **Consistency**: Ensuring continuity and coherence in learning. - -Remember, while free tutorials are abundant, investing in a structured resource can significantly expedite your learning process and provide a solid understanding of programming fundamentals essential for SeaTable development. - -!!! success "This are personal recommendations" - - The following sources does not contain any affiliate links and we do not earn any money from these recommendations. These are just good sources that we have used ourselves in the past. - -=== "Javascript" - - **Free online course** - - : A solid and __free online__ course is available from codecademy.com. The course [Learn JavaScript](https://www.codecademy.com/learn/introduction-to-javascript) requires a registration but is free and teaches you in approx. 20 hours all necessary skills. - - **Best online course** - - : The best __online course__ on javascript comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with Javascript in his course [The Ultimate JavaScript Series](https://codewithmosh.com/p/ultimate-javascript-series). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. - - **Book for Beginners** - - : If you prefer a __book__, then we can recommend [JavaScript from Beginner to Professional](https://www.amazon.de/JavaScript-Beginner-Professional-building-interactive/dp/1800562527/). It gives you all the basics for your first steps with Javascript. - -=== "Python" - - **Free online course** - - : An easy to follow beginner guide comes from Google. At [https://developers.google.com/edu/python?hl=de](https://developers.google.com/edu/python?hl=de) you can find this well balanced course to learn how to do your first steps. - - **Best online course** - - : The best __online course__ on Python comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with Python in his course [Complete Python Mastery](https://codewithmosh.com/p/python-programming-course-beginners). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. - - **Book for Beginners** - - : Our recommended book for beginners is called [Learn Python in One Day and Learn It Well](https://www.amazon.de/Python-2nd-Beginners-Hands-Project-ebook/dp/B071Z2Q6TQ) and as far as we can tell it keeps his promise. Most of our working students have read this book if they want to learn more about Python. - -=== "React" - - **Free online course** - - : This free online course comes to you from [Scrimba](https://scrimba.com/). Scrimba is a coding bootcamp with mainly paid courses and a high amount of interactive screencasts. The react course [Learn React](https://scrimba.com/learn/learnreact) is fortunately free of charge. - - **Best online course** - - : The best __online course__ on React comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh will guide and teach you React until and will build a complete Video Game Discovery App. The course is called [React 18 for Beginners](https://codewithmosh.com/p/ultimate-react-part1). diff --git a/docs/scripts/index.md b/docs/scripts/index.md index 8095f8f0..78f4b406 100644 --- a/docs/scripts/index.md +++ b/docs/scripts/index.md @@ -2,25 +2,28 @@ ## Supported scripting languages and requirements -Scripts are used to interact with the data in a base. SeaTable supports scripts written in Python and JavaScript (JS). +Scripts are used to interact with the data in a base. SeaTable supports scripts written in JavaScript and Python. Both languages have different requirements and abilities. Let's try to summarize these to help you make the right choice depending on your needs. -JS code is executed directly in the user's browser, requires no authentication, and is used for simple data operations. - -Python scripts, by contrast, are executed on a server or locally. As a consequence, Python scripts must authenticate against SeaTable Server. They are also suitable for more complex data processing scenarios. - -JS and Python scripts can be composed and executed directly in a SeaTable base. -![Screenshot of script icon in SeaTable](/media/Anlegen-eines-Skriptes.jpg) - -The execution of JS scripts in SeaTable has no requirements. - -To run Python scripts in SeaTable, the so-called [Python Pipeline](https://admin.seatable.com/installation/components/python-pipeline/) must be installed. You can also choose to run scripts [locally](https://developer.seatable.com/scripts/python/common_questions/#how-to-make-the-script-support-both-local-and-cloud-run). Local execution is convenient for development and debugging purposes. Scripts can also be easily integrated into larger projects. +| | JavaScript | Python | +|-----------------------|-------------|------------| +| Requirements | None | Eventually `seatable-api` library or [Python Pipeline](https://admin.seatable.com/installation/components/python-pipeline/) (see Execution environment) | +| Data operations | Simple (mainly tailored for single-line operations) | More complex (more available operations, possibility of using external libraries) | +| Execution environment | In SeaTable | - In SeaTable (self-hosted with the [Python Pipeline](https://admin.seatable.com/installation/components/python-pipeline/) installed, or Cloud)
- [Locally](https://developer.seatable.com/scripts/python/common_questions/#how-to-make-the-script-support-both-local-and-cloud-run) or on a server (need to install `seatable-api` library) | +| Authentication | Not needed | Needed | +| Main advantage | - Ready to use (no requirement, no authentication)
- Rather simple (both advantage and disadvantage) | - Local execution convenient for development and debugging purposes
- Easily integrated into larger projects | ## How to start? -Each chapter provides you with explanations about the available objects and methods. Multiple examples should help you to start immediately and get a feeling about the possibilities. +Both JavaScript and Python scripts can be composed and executed directly in a SeaTable base. -Here are some additional help articles from the [user manual](https://help.seatable.com) explaining how to create, execute and monitor a script in SeaTable: +![Screenshot of script icon in SeaTable](/media/Anlegen-eines-Skriptes.jpg) + +Here are some additional help articles from the [User Manual's scripts section](https://seatable.com/help/scripts) explaining how to create, execute and monitor a script in SeaTable: - [Creating and deleting a script](https://seatable.com/help/anlegen-und-loeschen-eines-skriptes/) - [Run script manually, by button or by automation](https://seatable.com/help/skript-manuell-per-schaltflaeche-oder-automation-ausfuehren/) - [The execution log of scripts](https://seatable.com/help/das-ausfuehrungslog-von-skripten/) + +You'll find in this manual a JavaScript and a Python section. For both of them, each chapter provides you with explanations about the available objects and methods (description with the eventual arguments and one or more simple use cases). + +Multiple [JavaScript](../scripts/javascript/examples/index.md) and [Python](../scripts/python/examples/index.md) examples should help you to start immediately and get a feeling about the possibilities. diff --git a/docs/scripts/javascript/basic_structure_js.md b/docs/scripts/javascript/basic_structure_js.md deleted file mode 100644 index da37e67d..00000000 --- a/docs/scripts/javascript/basic_structure_js.md +++ /dev/null @@ -1,118 +0,0 @@ -# Basic structure - -The JavaScript script runs directly in the current browser and is suitable for simple data processing. JavaScript does not require any authentication. - -!!! warning "Two JavaScript APIs in SeaTable" - - SeaTable offers two different ways to use JavaScript with SeaTable. You can executing a JS-Script directly in SeaTable and there is a JavaScript Client API. The functions are similar but not identical. - If you want to use a JavaScript in SeaTable, stay at this section, otherwise switch to the [Client APIs](../../clients/javascript/javascript_api.md). - -## Interact with your base - -JavaScript provide pre-defined objects, corresponding methods of this objects and utilities. On top, normal JavaScript operations like `console.log` or calculations are working as usual. - -- [base](/scripts/javascript/objects/base/) -- [output](/scripts/javascript/objects/output/) -- [context](/scripts/javascript/objects/context/) -- [base utilities](/scripts/javascript/objects/utilities/) - -## Let's beginn - -Let's make this concrete and let us look at some basic examples. - -1. Jump to your seatable webinterface -2. Create a new Script of the type `Javascript` -3. Copy the following code -4. Run the script - -You will learn from these examples, that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. - -=== "Get number of tables" - - ``` js - const tables = base.getTables(); // (1)! - output.text(tables.length); // (2)! - ``` - - 1. 1. `base` is the predefined-object provided by SeaTable containing all bases of a base. - 1. `getTables()` is the function to get all bases from the object `base`. - - 2. 1. `output` is also a predefined-object provided by SeaTable. - 1. `length` is just a normal operation in JavaScript. - - As you can see, the script will output the number of tables in your base. Read the comments behind the two lines to get more information about the difference between a predefined object, a method of this object and an ordinary JavaScript function. - -=== "Get column names" - - ```js - const table = base.getTableByName('Table1'); - const columns = base.getColumns(table); - - for (var i=0; i>> new row <<<') - output.text(row); - output.text(row['Name']); - } - ``` - - 1. get the content of the table `Table1`. - 2. get the content of the view `Default View`. - 3. get the row of this view `Default View` in this table `Table1`. - 4. iterate over all rows and print them - - This time, we will get the `Name` of all columns in the table `Table1` and the view `Default View`. - -=== "Write new row" - - ``` js - const table = base.getTableByName('Table1'); // (1)! - - const newRow = { // (2)! - 'Name': 'Hugo', - 'Age': 3, - }; - - try { - const row = base.addRow(table, newRow); - output.text(`New row added with _id: ${row._id}`); - } catch (error) { - output.text(`Error adding row: ${error}`); - } - ``` - - 1. Replace `Table1` with your actual table name - 2. Update column names `Name` and `Age` and the values you would like to add. - -=== "Update one specific cell" - - ``` js - // Get the table - const table = base.getTableByName('Table1'); - - // Specify the row_id you want to update - const rowId = 'KDW9PZMkTOuwtx71pmAMxA'; - - // Define the updates you want to make - // Replace 'Name' with the actual column name you want to update - // and 'NewValue' with the new value you want to set - const updates = { - 'Name': 'NewValue' - }; - - base.updateRow(table, rowId, updates); - ``` diff --git a/docs/scripts/javascript/common_questions.md b/docs/scripts/javascript/common_questions.md index 0cf80d53..4487880c 100644 --- a/docs/scripts/javascript/common_questions.md +++ b/docs/scripts/javascript/common_questions.md @@ -1,13 +1,72 @@ # Common questions (JavaScript) -??? question "How to output a variable?" +??? question "How to output the content of a variable?" - To output the content of a variable you should use quotation mark instead of single or double quotes. + To output the content of a variable you should use either no punctuation mark at all (for variable only) or dollar/brackets inside backticks `` `${}` ``. + + + ```js + const myVariable = 462; + // variable-only output + output.text(myVariable); /* (1)! */ + + // prettier output formatting + output.text(`the content of my variable is ${myVariable}`); /* (2)! */ + + // Simple/Double quotes won't work as they are used to encapsulate strings + output.text("myVariable"); /* (3)! */ + output.text('myVariable'); /* (4)! */ + ``` + + 1. Returns `462` + + 2. Returns `the content of my variable is 462` + + 3. Returns `myVariable` + + 4. Returns `myVariable` + +??? question "The display of complex elements (tables, arrays of rows) is sometimes difficult to read" + + Do not hesitate to use `console.log` and to check your browser console. Otherwise, you could try to use this function (or to create your own) at the beginning of your scripts: ```js - # correct way to output the variable - `name_of_variable` + function jsonPrettyFormat(json, indent=0) { + const indenterChar = " "; /* (1)! */ + if (json instanceof Array) { + output.text(indenterChar.repeat(indent) + "["); + indent += 1; + json.forEach((elem)=>jsonPrettyFormat(elem,indent)); + indent -= 1; + output.text(indenterChar.repeat(indent) + "]"); + } + else { + if (!(typeof(json)=="object")) { + output.text(indenterChar.repeat(indent) + json); + } else { + output.text(indenterChar.repeat(indent) + "{"); + indent += 1; + for (const [key, value] of Object.entries(json)) { + if (!(typeof(value)=="object")) { + output.text(indenterChar.repeat(indent) + key + ": " + value) + } else { + output.text(indenterChar.repeat(indent) + key + ": "); + indent += 1; + jsonPrettyFormat(value,indent); + } + } + indent -= 1; + output.text(indenterChar.repeat(indent) + "}"); + } + } + } + ``` + + 1. Please note that the indent character is not a classic space character as the output window of SeaTable's script editor actually trims indent spaces. - # will not work - "name_of_variable" + Just call it on an object to see the result + + ```js + let rows = base.getRows('Daily expenses', 'Default View'); + jsonPrettyFormat(rows); ``` diff --git a/docs/scripts/javascript/examples/auto-add-rows.md b/docs/scripts/javascript/examples/auto-add-rows.md index 404fd8ca..20cb4ca0 100644 --- a/docs/scripts/javascript/examples/auto-add-rows.md +++ b/docs/scripts/javascript/examples/auto-add-rows.md @@ -1,27 +1,47 @@ -# Example: Automatically calculate... +{% + include-markdown "includes.md" + start="" + end="" +%} + +With JavaScript scripts, you have to ensure **before** running the script that the options you want to add (in this case `Cloud service` and `Daily office`) already exist in your "single select"-type column. ``` javascript -// Automatically record monthly repetitive items in a ledger +// Record monthly repetitive expenses in a ledger const table = base.getTableByName('Daily expenses'); -// get date objects on the 10th and 20th of the current month +// Get date objects on the 10th and 20th of the current month var date = new Date(); var date10 = new Date(date.setDate(10)); var date20 = new Date(date.setDate(20)); -// create two new expense items -var feeAWS = {'Name': 'Amazon cloud service', - 'Date': base.utils.formatDate(date10), - 'Type': 'Cloud service' - }; -var feeClean = {'Name': 'Clean', - 'Date': base.utils.formatDate(date20), - 'Type': 'Daily office', - 'Fee': 260 +// Check if the monthly expense items have already been created and eventually create them +const AWSCondition = "Name='Amazon Cloud Service' and Date='" + base.utils.formatDate(date10) + "'"; +const feeAWSCurrentMonth = base.filter('Daily expenses', 'Default View', AWSCondition); +if (feeAWSCurrentMonth.count() == 0) { + var feeAWS = { 'Name': 'Amazon Cloud Service', + 'Date': base.utils.formatDate(date10), + 'Type': 'Cloud service', + 'Type (single select)': 'Cloud service', }; +} +const CleanCondition = "Name='Clean' and Date='" + base.utils.formatDate(date20) + "'"; +const feeCleanCurrentMonth = base.filter('Daily expenses', 'Default View', CleanCondition); +if (feeCleanCurrentMonth.count() == 0) { + var feeClean = { 'Name': 'Clean', + 'Date': base.utils.formatDate(date20), + 'Type': 'Daily office', + 'Type (single select)': 'Daily office', + 'Fee': 260 + }; +} -// auto add data -base.addRow(table, feeAWS); -base.addRow(table, feeClean); -``` \ No newline at end of file +// Auto add data (if needed) +if (feeAWS) { + base.appendRow(table, feeAWS); +} +if (feeClean) { + base.appendRow(table, feeClean); +} +``` diff --git a/docs/scripts/javascript/examples/calculate-accumulated-value.md b/docs/scripts/javascript/examples/calculate-accumulated-value.md new file mode 100644 index 00000000..53d953f4 --- /dev/null +++ b/docs/scripts/javascript/examples/calculate-accumulated-value.md @@ -0,0 +1,58 @@ +{% + include-markdown "includes.md" + start="" + end="" +%} + +``` javascript +// Accumulates the values of the current row and the previous rows, and records the result to the current row +const tableName = 'Accumulated value'; +const viewName = 'Default View'; + +// Name of the column that records total number at a specific time +const valueColumnName = 'Value to add'; +// Name of the column that need to calculate incremental value +const incrementalColumnName = 'Incremental total'; + +const table = base.getTableByName(tableName); +const view = base.getViewByName(table, viewName); + +// If current view is a grouped view +if (view.groupbys && view.groupbys.length > 0) { + // Get group view rows + const groupViewRows = base.getGroupedRows(table, view); + + groupViewRows.map((group) => { + let incrementalTotal = 0; + group.rows.map((row, rowIndex, rows) => { + // Get current row value + const currentNumber = row[valueColumnName]; + if (!currentNumber) return; + // Calculate increment + const previousRow = rows[rowIndex - 1]; + // If there is no previousRow, set increaseCount to 0 + const previousNumber = rowIndex>0 ? incrementalTotal : 0; + const increaseCount = currentNumber + previousNumber; + incrementalTotal = increaseCount; + // Set calculated increment to row + base.updateRow(table, row, {[incrementalColumnName]: increaseCount}); + }); + }); +} else { + // Get current view rows + let incrementalTotal = 0; + const rows = base.getRows(table, view); + rows.forEach((row, rowIndex, rows) => { + // Calculate increment + const currentNumber = row[valueColumnName]; + if (!currentNumber) return; + const previousRow = rows[rowIndex - 1]; + // If there is no previousRow, set increaseCount to 0 + const previousNumber = rowIndex>0 ? incrementalTotal : 0; + const increaseCount = currentNumber + previousNumber; + incrementalTotal = increaseCount; + // Set calculated increment to row + base.updateRow(table, row, {[incrementalColumnName]: increaseCount}); + }); +} +``` \ No newline at end of file diff --git a/docs/scripts/javascript/examples/compute-attendance-statistics.md b/docs/scripts/javascript/examples/compute-attendance-statistics.md new file mode 100644 index 00000000..e4ebb1a0 --- /dev/null +++ b/docs/scripts/javascript/examples/compute-attendance-statistics.md @@ -0,0 +1,83 @@ +{% + include-markdown "includes.md" + start="" + end="" +%} + +``` javascript +// Computes, from a list of clocking times, daily clock in (earliest clocking) +// and clock out (latest clocking) times for each day and staff member +const originTableName = 'Clocking table'; +const originViewName = 'Default View'; +const originNameColumnName = 'Name'; +const originDepartmentColumnName = 'Department'; +const originDateColumnName = 'Date'; +const originTimeColumnName = 'Clocking time'; + +const targetTableName = 'Attendance statistics'; +const targetNameColumnName = 'Name'; +const targetDepartmentColumnName = 'Department'; +const targetDateColumnName = 'Date'; +const targetStartTimeColumnName = 'Clock-in'; +const targetEndTimeColumnName = 'Clock-out'; +const targetTable = base.getTableByName(targetTableName); + +const table = base.getTableByName(originTableName); +const view = base.getViewByName(table, originViewName); +const rows = base.getRows(table, view); + +// Sort the rows in the table according to the date column; +rows.sort((row1, row2) => { + if (row1[originDateColumnName] < row2[originDateColumnName]) { + return -1; + } else if (row1[originDateColumnName] > row2[originDateColumnName]) { + return 1; + } else { + return 0; + } +}); + +/* + Group all rows via date and save them to groupedRows, the format + of the object is {'2020-09-01': [row, ...], '2020-09-02': [row, ...]} +*/ +const groupedRows = {}; +rows.forEach((row) => { + const date = row[originDateColumnName]; + if (!groupedRows[date]) { + groupedRows[date] = []; + } + groupedRows[date].push(row); +}); + +const dateKeys = Object.keys(groupedRows); + +// Traverse all the groups in groupedRows +dateKeys.forEach((dateKey) => { + // Get all clocking data of all members for the current date + const dateRows = groupedRows[dateKey]; + const staffDateStatItem = {}; + // Traverse these rows and group by the name of the employee, get the clock-in and clock-out time of each employee that day, and save it to staffDateStatItem + // the format is { EmployeeName: {Name: 'EmployeeName', Date: '2020-09-01', Clock-in: '08:00', Clock-out: '18:00'},... } + dateRows.forEach((row)=> { + const name = row[originNameColumnName]; + if (!staffDateStatItem[name]) { + // Generate a new row based on the original row data, and add Clock-in and Clock-out columns in the newly generated row + staffDateStatItem[name] = { [targetNameColumnName]: name, [targetDateColumnName]: row[originDateColumnName], [targetDepartmentColumnName]: row[originDepartmentColumnName], [targetEndTimeColumnName]: row[originTimeColumnName], [targetStartTimeColumnName]: row[originTimeColumnName]}; + } else { + // When another record (same employee and same date) is found, compare the time, choose the latest one as the Clock-out time, and the earliest one as the Clock-in time + const time = row[originTimeColumnName]; + const staffItem = staffDateStatItem[name]; + if (staffItem[targetStartTimeColumnName] > time) { + staffItem[targetStartTimeColumnName] = time; + } else if (staffItem[targetEndTimeColumnName] < time) { + staffItem[targetEndTimeColumnName] = time; + } + } + }); + // Write the attendance data of all employees on the current date into the table + Object.keys(staffDateStatItem).forEach((name) => { + base.appendRow(targetTable, staffDateStatItem[name]); + }); +}); +``` \ No newline at end of file diff --git a/docs/scripts/javascript/examples/get-incremental.md b/docs/scripts/javascript/examples/get-incremental.md deleted file mode 100644 index 173156cd..00000000 --- a/docs/scripts/javascript/examples/get-incremental.md +++ /dev/null @@ -1,53 +0,0 @@ -# Get incrementals - - -``` javascript -const tableName = 'A table'; -const viewName = 'Default view'; - -// name of the column that records total number at a specific time -const columnName = 'Total number'; - -// name of the column that need to calculate incremental value -const incrementalColumnName = 'Incremental number'; - -// get table -const table = base.getTableByName(tableName); -// get view -const view = base.getViewByName(table, viewName); - -// if current view is a grouped view -if (view.groupbys && view.groupbys.length > 0) { - // get group view rows - const groupViewRows = base.getGroupedRows(table, view); - - groupViewRows.map((group) => { - group.rows.map((row, index, rows) => { - // get current row value - const currentNumber = row[columnName]; - if (!currentNumber) return; - // calculate increment - const previousRow = rows[index - 1]; - // if there is no previousRow, set increaseCount to 0 - const previousNumber = previousRow ? previousRow[columnName] : currentNumber; - const increaseCount = currentNumber - previousNumber; - // set calculated increment to row - base.modifyRow(table, row, {[incrementalColumnName]: increaseCount}); - }); - }); -} else { - // get current view rows - const rows = base.getRows(table, view); - rows.map((row, rowIndex, rows) => { - // calculate increment - const currentNumber = row[columnName]; - if (!currentNumber) return; - const previousRow = rows[rowIndex - 1]; - // if there is no previousRow, set increaseCount to 0 - const previousNumber = previousRow ? previousRow[columnName] : currentNumber; - const increaseCount = currentNumber - previousNumber; - // set calculated increment to row - base.modifyRow(table, row, {[incrementalColumnName]: increaseCount}); - }); -} -``` \ No newline at end of file diff --git a/docs/scripts/javascript/examples/index.md b/docs/scripts/javascript/examples/index.md index 301e261b..e9d1d300 100644 --- a/docs/scripts/javascript/examples/index.md +++ b/docs/scripts/javascript/examples/index.md @@ -1,15 +1,21 @@ # Examples -Currenty this documentation contains three examples for easy to follow script with Javascript. You can just copy&paste them in any base in SeaTable any run them. +This documentation currently contains three easy-to-follow examples of JavaScript scripts. For each example, you'll need a special base structure so that you can just copy&paste the scripts into SeaTable and run them. ## Add rows -short explanation will follow soon. +This script demonstrates how to add rows to record monthly repetitive expenses in a ledger. -## Get Incremental +[read more :material-arrow-right-thin:](/scripts/javascript/examples/auto-add-rows/) -short explanation will follow soon. +## Calculate accumulated value + +This script computes an accumulated value (adds the value of the current row and the previous rows), similar to the *Calculate accumulated value* operation from the data processing menu. + +[read more :material-arrow-right-thin:](/scripts/javascript/examples/calculate-accumulated-value/) ## Statistics -short explanation will follow soon. +This script computes, from a list of clocking times, daily clock in (earliest clocking) and clock out (latest clocking) times for each day and staff member. + +[read more :material-arrow-right-thin:](/scripts/javascript/examples/compute-attendance-statistics/) diff --git a/docs/scripts/javascript/examples/statistics-attendance.md b/docs/scripts/javascript/examples/statistics-attendance.md deleted file mode 100644 index 8e653a44..00000000 --- a/docs/scripts/javascript/examples/statistics-attendance.md +++ /dev/null @@ -1,95 +0,0 @@ -# staticstics - -``` javascript -const originTableName = 'Attendance table'; -const originViewName = 'Default view'; -const originNameColumnName = 'Name'; -const originDepartmentColumnName = 'Department'; -const originDateColumnName = 'Date'; -const originTimeColumnName = 'Attendance time'; - -const targetTableName = 'Statistics attendance'; -const targetNameColumnName = 'Name'; -const targetDepartmentColumnName = 'Department'; -const targetDateColumnName = 'Date'; -const targetStartTimeColumnName = 'Clock-In'; -const targetEndTimeColumnName = 'Clock-Out'; -const targetTable = base.getTableByName(targetTableName); - -const table = base.getTableByName(originTableName); -const view = base.getViewByName(table, originViewName); -const rows = base.getRows(table, view); - -// sort the rows in the table according to the date column; -rows.sort((row1, row2) => { - if (row1[originDateColumnName] < row2[originDateColumnName]) { - return -1; - } else if (row1[originDateColumnName] > row2[originDateColumnName]) { - return 1; - } else { - return 0; - } -}); - -/* - group all rows via date and save them to groupedRows, the format - of the object is {'2020-09-01': [row, ...], '2020-09-02': [row, ...]} -*/ -const groupedRows = {}; -rows.forEach((row) => { - const date = row[originDateColumnName]; - if (!groupedRows[date]) { - groupedRows[date] = []; - } - groupedRows[date].push(row); -}); - -const dateKeys = Object.keys(groupedRows); - -// traverse all the groups in groupedRows -dateKeys.forEach((dateKey) => { - // get all attendance data of all members on the current date - const dateRows = groupedRows[dateKey]; - const staffDateStatItem = {}; - // traverse these rows of data and group by the name of the employee, get the clock-in and clock-out time of each employee that day, and save it to staffDateStatItem - // the format is { a1: {Name: 'a1', Date: '2020-09-01', Clock-In: '08:00', Clock-Out: '18:00'},... } - dateRows.forEach((row)=> { - const name = row[originNameColumnName]; - if (!staffDateStatItem[name]) { - // Generate a new row based on the original row data, and add Clock-In and Clock-Out columns in the newly generated row - staffDateStatItem[name] = { [targetNameColumnName]: name, [targetDateColumnName]: row[originDateColumnName], [targetDepartmentColumnName]: row[originDepartmentColumnName], [targetEndTimeColumnName]: row[originTimeColumnName], [targetStartTimeColumnName]: row[originTimeColumnName]}; - } else { - // when the column name of the row is repeated, compare the time, choose the largest one as the Clock-Out time, and the smallest one as the Clock-In time - const time = row[originTimeColumnName]; - const staffItem = staffDateStatItem[name]; - if (compareTime(staffItem[targetStartTimeColumnName], time)) { - staffItem[targetStartTimeColumnName] = time; - } else if (compareTime(time, staffItem[targetEndTimeColumnName])) { - staffItem[targetEndTimeColumnName] = time; - } - } - }); - - // write the attendance data of all employees on the current date into the table - Object.keys(staffDateStatItem).forEach((name) => { - base.addRow(targetTable, staffDateStatItem[name]); - }); -}); - -// compare the size of two string format time -function compareTime(time1, time2) { - const t1 = time1.split(':'); - const t2 = time2.split(':'); - if (parseInt(t1[0]) > parseInt(t2[0])) { - return true; - } else if (parseInt(t1[0]) < parseInt(t2[0])) { - return false; - } else if (parseInt(t1[0]) == parseInt(t2[0])) { - if (parseInt(t1[1]) > parseInt(t2[1])) { - return true; - } else { - return false; - } - } -} -``` \ No newline at end of file diff --git a/docs/scripts/javascript/objects/columns.md b/docs/scripts/javascript/objects/columns.md index 42b15bb1..883e4a67 100644 --- a/docs/scripts/javascript/objects/columns.md +++ b/docs/scripts/javascript/objects/columns.md @@ -1,110 +1,139 @@ # Columns -## Get Columns +You'll find below all the available methods to interact with the columns of a SeaTable table. -!!! question "getColumns" +{% + include-markdown "includes.md" + start="" + end="" +%} - Get all the columns in the table, and return all the column objects in an array. +## Get Column(s) + +!!! abstract "getColumnByName" + + Get the column object of a particular `table`, specified by the column `name`. ``` js - base.getColumns(table: Object/String); + base.getColumnByName(table: Object/String/* (1)! */, name: String); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Single column object (`undefined` if column `name` doesn't exist) + + __Example__ ``` js const table = base.getTableByName('Table1'); - const columns = base.getColumns(table); - - column.forEach((column) => { - output.text(column.name); - }) + const column = base.getColumnByName(table, 'Column name'); + output.text(column.name); ``` ``` js - const columns = base.getColumns('Table1'); + const column = base.getColumnByName('Table1', 'Column name'); ``` -!!! question "listColumns" +!!! abstract "getColumns" - ```` - Get the columns by name of table and view, if view_name is not set, all columns in table will be returned + Get all the columns of a specific `table`. ``` js - base.listColumns(table_name: String, view_name: String); + base.getColumns(table: Object/String/* (1)! */); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Array of column objects (throws an error if `table` doesn't exist) + + __Example__ ``` js - const table_name = 'Table1' - const view_name = 'Default' - const columns = base.listColumns(table_name, view_name); + const table = base.getTableByName('Table1'); + const columns = base.getColumns(table); - column.forEach((column) => { + columns.forEach((column) => { output.text(column.name); }) ``` ``` js - const columns = base.listColumns('Table1'); + const columns = base.getColumns('Table1'); ``` - ```` -!!! question "getShownColumns" +!!! abstract "listColumns" - Get all the displayed columns in a view, excluding the hidden columns in the view, and return an array. + Get the columns of a table (specified by its name `tableName`), optionally from a specific view (specified by its name `viewName`). If `viewName` is not set, all columns of the table will be returned (equivalent, in this case, to `base.getColumns`). ``` js - base.getShownColumns(table: Object/String, view: Object/String); + base.listColumns(tableName: String, viewName: String); ``` - __Examples__ + __Output__ Array of column objects (throws an error if `table` doesn't exist) + + __Example__ ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'view 1'); - const columns = base.getShownColumns(table, view); - column.forEach((column) => { + const tableName = 'Table1' + const viewName = 'Default View' + const columns = base.listColumns(tableName, viewName); + + columns.forEach((column) => { output.text(column.name); }) ``` ``` js - const columns = base.getShownColumns('Table1', 'view 1'); + const columns = base.listColumns('Table1'); ``` -!!! question "getColumnByName" +!!! abstract "getShownColumns" - Get the column object via its name. + Get all the columns of a `table` displayed in a specific `view` (hidden columns are not returned). ``` js - base.getColumnByName(table: Object/String, name: String); + base.getShownColumns(table: Object/String, view: Object/String/* (1)! */); ``` - __Examples__ + 1. `table`: either a table object or the table name + + `view` (required): either a view object or the view name + + __Output__ Array of column objects (throws an error if `table` or `view` doesn't exist) + + __Example__ ``` js - const column = base.getColumnByName(table, 'Column name'); - output.text(column.name); + const table = base.getTableByName('Table1'); + const view = base.getViewByName(table, 'Default View'); + const columns = base.getShownColumns(table, view); + columns.forEach((column) => { + output.text(column.name); + }) ``` ``` js - const column = base.getColumnByName('Table1', 'Column name'); + const columns = base.getShownColumns('Table1', 'Default View'); ``` -!!! question "getColumnsByType" +!!! abstract "getColumnsByType" - Get all specific types of columns in the table. + Get all the columns of a specific `type` in a `table`. ``` js - const columns = base.getColumnsByType(table: Object/String, type: String); + base.getColumnsByType(table: Object/String, type: String /* (1)! */); ``` - __Examples__ + 1. `table`: either a table object or the table name + + `type` (required): the type of the column (see the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for supported types) + + __Output__ Array of column objects (empty array if no corresponding columns or wrong `type`) + + __Example__ ``` js const table = base.getTableByName('Table1'); const columns = base.getColumnsByType(table, 'text'); - output.text(column.length); + output.text(columns.length); ``` ``` js const columns = base.getColumnsByType('Table1', 'text'); - output.text(column.length); + output.text(columns.length); ``` diff --git a/docs/scripts/javascript/objects/context.md b/docs/scripts/javascript/objects/context.md index ced716ca..1f51b909 100644 --- a/docs/scripts/javascript/objects/context.md +++ b/docs/scripts/javascript/objects/context.md @@ -1,33 +1,37 @@ # Context -When the script runs, the context object provides the context. The usage is as follows. +When the script runs, the `context` object context-related elements. The usage is as follows. -!!! info "currentTable" +!!! abstract "currentTable" - Returns the name of the currently selected table. + Currently selected table. ``` js - base.context.currentTable + base.context.currentTable; ``` + __Output__ The name of the currently selected table + __Example__ ``` js - const name = base.context.currentTable - output.text(`The name of the current table is: ${name}`) + const name = base.context.currentTable; + output.text(`The name of the current table is: ${name}`); ``` -!!! info "currentRow" - - Returns the currently selected row and returns the complete row object including `_id`, `_mtime`, `_ctime`. If no row is selected, this function returns `undefined`. +!!! abstract "currentRow" + Currently selected row. If the script is launched from a button click, this is the row on which the button was clicked. + ``` js - base.context.currentRow + base.context.currentRow; ``` + __Output__ Complete row object, including `_id`, `_mtime`, `_ctime`. If no row is selected, this function returns `undefined`. + __Example__ ``` js - const row = base.context.currentRow - output.text(row) + const row = base.context.currentRow; + output.text(row); ``` diff --git a/docs/scripts/javascript/objects/index.md b/docs/scripts/javascript/objects/index.md index 4ad1e64c..9ebbbe2f 100644 --- a/docs/scripts/javascript/objects/index.md +++ b/docs/scripts/javascript/objects/index.md @@ -1,15 +1,132 @@ -# Predefined objects in SeaTable (Javascript) +# Predefined objects in SeaTable (JavaScript) -JavaScript in SeaTable relies on the JavaScript library `seatable-api-js` which can be found on [GitHub](https://github.com/seatable/seatable-api-js). +The JavaScript scripts run directly in the current browser and are suitable for simple data processing. JavaScript does not require any authentication. -This manual list all available objects and methods that are availabe within Javascript scripts in SeaTable. +This manual list all available objects and methods (also called functions) that are available within JavaScript scripts in SeaTable. On top, normal JavaScript operations like `console.log` or calculations are working as usual. By running directly in SeaTable, JavaScript scripts have the ability to access the [base context](./context.md). [Base utilities](./utilities.md) and specific [output methods](./output.md) are also available. Unless otherwise stated, **all method arguments are required**. -!!! Hint "Need specific function?" +!!! warning "Two JavaScript APIs in SeaTable" + + SeaTable offers two different ways to use JavaScript with SeaTable. You can executing a JavaScript script directly in SeaTable and there is a JavaScript Client API. The functions are similar but not identical. + If you want to use a JavaScript script in SeaTable, stay at this section, otherwise switch to the [Client APIs](/clients/javascript/javascript_api). + +## Data model + +{% + include-markdown "includes.md" + start="" + end="" +%} + +!!! info "Need a specific function?" The JavaScript class does not yet cover all available functions of the SeaTable API. If you are missing a special function, please contact us at [support@seatable.io](mailto:support@seatable.io) and we will try to add the missing functions. -For a more detailed description of the used parameters, refer to the data model at the [SeaTable API Reference](https://api.seatable.com/reference/models). +## Getting started + +Let's have a look at some basic examples. You will learn that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. Here is how to run these examples in your environment: + +1. Jump to your SeaTable web interface +2. Create a new Script of the type `JavaScript` +3. Copy the following code (you might have to change tables' or columns' names) +4. Run the script + +=== "Get number of tables" + + ``` js + const tables = base.getTables(); // (1)! + output.text(tables.length); // (2)! + ``` + + 1. 1. `base` is the predefined-object provided by SeaTable containing all tables of a base. + 1. `getTables()` is the function to get all bases from the object `base`. + + 2. 1. `output` is also a predefined-object provided by SeaTable. + 1. `length` is just a pure JavaScript property. + + As you can see, the script will output the number of tables in your base. Read the comments at the end of both lines to get more information about the difference between a predefined object, a function of this object and a pure JavaScript property. + +=== "Get column names" + + ```js + const table = base.getTableByName('Table1'); // (1)! + const columns = base.getColumns(table); // (2)! + + for (var i=0; i>> new row <<<') + output.text(row); + output.text(row['Name']); + } + ``` + + 1. get the content of the table `Table1` (replace `Table1` with your actual table name). + 2. get the content of the view `Default View`. + 3. get the rows displayed in the view `Default View` of the table `Table1`. + 4. iterate over all rows and print them + + This time, we will get content of the `Name` column for each row displayed in the view `Default View` of the table `Table1`. + +=== "Write new row" + + ``` js + const table = base.getTableByName('Table1'); // (1)! + + const newRow = { // (2)! + 'Name': 'Hugo', + 'Age': 3, + }; + + try { + const row = base.addRow(table, newRow); + output.text(`New row added with _id: ${row._id}`); + } catch (error) { + output.text(`Error adding row: ${error}`); + } + ``` + + 1. get the content of the table `Table1` (replace `Table1` with your actual table name). + 2. create an object containing column names `Name` and `Age` and the values you would like to set. + +=== "Update one specific row" + + ``` js + // Get the table + const table = base.getTableByName('Table1'); + + // Specify the row_id you want to update + const rowId = 'KDW9PZMkTOuwtx71pmAMxA'; // (1)! + + // Define the updates you want to make + // Replace 'Name' with the actual column name you want to update + // and 'NewValue' with the new value you want to set + // You can define more key:value pairs if you want to update + // several values of the row at the same time + const updates = { + 'Name': 'NewValue' + }; + + base.updateRow(table, rowId, updates); // (2)! + ``` -## Base object + 1. define the id of the row you want to modify. You can also use `base.context.currentRow;` to access the current (selected) row. + 2. update each values contained in the object `updates` of the row whose id is `rowId` in the table `Table1`. -Base object provide a way to read, manipulate and output data in/from your base. The following methods are available. + Do not hesitate to write comments in your code. It will help you (or others) to understand it more easily afterwards. diff --git a/docs/scripts/javascript/objects/links.md b/docs/scripts/javascript/objects/links.md index a068dc45..4e1b1c82 100644 --- a/docs/scripts/javascript/objects/links.md +++ b/docs/scripts/javascript/objects/links.md @@ -1,114 +1,223 @@ # Links -## Get Links +!!! warning "link id and column key" -!!! question "getLinkedRecords" + `linkId` should not be mistaken with the column `key`! The `key` value is unique (like an id) whereas the link id will be shared between the two linked columns. Please note that `linkId` is used as argument to add/update/remove links, whereas you'll have to provide `linkColumnKey` (the link column `key`) to get linked records. Both information are available in the column object: + + ```json + { +  "key": "Cp51", /* (1)! */ +  "type": "link", +  "name": "Link column", +  "editable": true, +  "width": 200, +  "resizable": true, +  "draggable": true, +  "data": { +   "display_column_key": "0000", +   "is_internal": true, +   "link_id": "UAmR", /* (2)! */ +   "table_id": "FJkA", /* (3)! */ +   "other_table_id": "nw8k", /* (4)! */ +   "is_multiple": true, +   "is_row_form_view": false, +   "view_id": "", +   "array_type": "text", +   "array_data": null, +   "result_type": "array" +  }, +  "permission_type": "", +  "permitted_users": [], +  "permitted_group": [], +  "edit_metadata_permission_type": "", +  "edit_metadata_permitted_users": [], +  "edit_metadata_permitted_group": [], +  "description": null, +  "colorbys": {} + } + ``` + + 1. The column `key` (referred as `linkColumnKey` in `base.getLinkedRecords` arguments) + + 2. The link id of the column (referred as `linkId` in the add/update/remove link(s) methods arguments) - List the linked records of rows. You can get the linked records of multiple rows. + 3. The table whose id is `table_id` is referred later in this section as the *source* table (the table containing this column) + + 4. The table whose id is `other_table_id` is referred later in this section as the *target* table + +## Get linkId + +!!! abstract "getColumnLinkId" + + Get the link id of the column `columnName` from the table `tableName`. ```js - base.getLinkedRecords(table_id, link_column_key, rows) + base.getColumnLinkId(tableName: String, columnName: String); ``` - * table_id: the id of link table - * link_column_key: the column key of the link column of link table ( not link_id ) - * rows: a list, each item of the which contains a row info including row_id, offset (defualt by 0) and limit (default by 10) of link table + __Output__ String (throws an error if table `tableName` or column `columnName` doesn't exist) __Example__ ```js - base.getLinkedRecords('0000', '89o4', [ - {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, - {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} - ]) - - // a key-value data structure returned as below - // key: row_id of link table - // value: a list which includes the row info of linked table - { - 'FzNqJxVUT8KrRjewBkPp8Q': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ], - 'Jmnrkn6TQdyRg1KmOM4zZg': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ] - } + base.getColumnLinkId('Table1', 'Table2 link'); ``` -## Get Link ID +## Get linked records + +!!! info "Rows and records, source and target" + + Rows and records are basically the same things. However, to make the following description easier to understand, we will differentiate them: + + - Rows are from the *source* table (the table whose id is `tableId`) + + - Records are the rows from the *target* table (the table linked to the *source* table in the column whose `key` is `linkColumnKey` or whose link id is `linkId`) -!!! question "getColumnLinkId" +!!! abstract "getLinkedRecords" - Get the link id by column name. + List the records linked (in the column whose `key` is `linkColumnKey`) to one or more rows of the *source* table. The row(s) you want to get the linked records from are defined in the `linkedRows` object (see below). ```js - base.getColumnLinkId(tableName, columnName) + await/* (1)! */ base.getLinkedRecords(tableId: String, linkColumnKey: String, linkedRows: Object) /* (2)! */; ``` + 1. `await` is used for asynchronous functions. This is **required** to ensure that the following operations (or the variable where you store the results) wait for the query's response to arrive before continuing to execute the script + + 2. `tableId`: the id of *source* table + + `linkColumnKey`: the column **key** of the link-type column of *source* table (**not** the link id from `base.getColumnLinkId`) + + `linkedRows`: an array of objects, each of them containing: + + - `row_id`: the id of the row we want to get the linked records from + + - `limit`: the maximum number of linked records to get (default is 10) + + - `offset`: the number of first linked records not to retrieve (default is 0) + + __Output__ A `key`:`value` data structure where each `key` is the id of a row of the *source* table and the corresponding value is an array of link objects (see Output structure example below) + __Example__ - ```js - base.getColumnLinkId('Table1', 'Record') - ``` + === "Function run" -## Add Links + ```js + await base.getLinkedRecords('0000', '89o4', [ + {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, + {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} + ]); + ``` -!!! question "addLink" + === "Output structure example" - Add link, link other table records. Get more information about linking columns from the [SeaTable API Reference](https://api.seatable.com/reference/create-row-link). + ```js + { + 'FzNqJxVUT8KrRjewBkPp8Q' /* (1)! */: [ + {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'} /* (2)! */, + {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, + ... + ], + 'Jmnrkn6TQdyRg1KmOM4zZg': [ + {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, + {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, + ... + ] + } + ``` - ```js - base.addLink(linkId, tableName, linkedTableName, rowId, linkedRowId) - ``` + 1. id of a row of the *source* table - __Example__ + 2. link object: + + - `row_id` is the id of the linked record (row from the *target* table) + - `display_value` is the displayed in the column whose `key` is `linkColumnKey` + (from a column of the *target* table) + + __Output__ Object containing the linked records for each row (see Output structure example above) + + __Example: Get linked records from current row__ ```js - base.addLink('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') + const table = base.getTableByName('Table1'); + const linkColumn = base.getColumnByName(table,'Table2 link'); + const currentRowLinks = await base.getLinkedRecords(table._id, linkColumn.key, [{'row_id': base.context.currentRow._id, 'limit':100 /* (1)! */}]); + currentRowLinks[base.context.currentRow._id].forEach((link) => {output.text(link)}); ``` -## Remove Links + 1. `limit`:100 => the response will return maximum 100 rows + -!!! question "removeLink" +## Add link - Delete the link row. +!!! abstract "addLink" + + Add link in a link-type column. You'll need the *source* target's name `tableName`, the *target* table's name `linkedTableName`, the `linkId` from the link-type column and both the ids of the rows you want to link: `rowId` for the row from the *source* table and `linkedRowId` for the record from the *target* table. ```js - base.removeLink(linkId, tableName, linkedTableName, rowId, linkedRowId) + base.addLink(linkId: String, tableName: String, linkedTableName: String, rowId: String, + linkedRowId: String); ``` + __Output__ Nothing + __Example__ ```js - base.removeLink('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') + base.addLink('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); ``` -## Update Links + __Example: Add link to current row__ + + ```js + // Do not hesitate to store the tables' and columns' names at the beginning of your script, + // it will make it really easier to update if names change + const table1Name = "Table1"; + const table1LinkColumnName = "Table2 link"; + const table2Name = "Table2"; + + const linId = base.getColumnLinkId(table1Name,table1LinkColumnName); /* (1)! */ + const currentRowId = base.context.currentRow._id; + base.addLink(linId, table1Name, table2Name, currentRowId, 'J5St2clyTMu_OFf9WD8PbA'); + ``` + + 1. Remember you can use `base.getColumnLinkId` to get the link id of a specific link-type column. + -!!! question "updateLinks" +## Update link(s) - Remove all existing row links and add new links. +!!! abstract "updateLinks" + + Update the content of the link-type column whose link id is `linkId` for the row with id `rowId` in the table `tableName`. It will remove all existing row links and add new links to records of table `linkedTableName` with ids listed in the `updatedlinkedRowIds` array. ```js - base.updateLinks(linkId, tableName, linkedTableName, rowId, updatedlinkedRowIds) + base.updateLinks(linkId, tableName, linkedTableName, rowId, updatedlinkedRowIds: Array of String); ``` + __Output__ Nothing + __Example__ ```js - const rows = base.getRows('contact', 'Default_view'); - // Update row links to [rows[0]._id, rows[1]._id, rows[2]._id, rows[3]._id] - base.updateLinks('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', [rows[0]._id, rows[1]._id, rows[2]._id, rows[3]._id]) + const records = base.getRows('Contacts', 'Default_view'); + // Update links for row from "Team Members" with _id CGtoJB1oQM60RiKT-c5J-g to [records[0]._id, records[1]._id, records[2]._id, records[3]._id] + // Real-life tip: ensure that the array "records" actually contains at least 4 elements! + base.updateLinks('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', [records[0]._id, records[1]._id, records[2]._id, records[3]._id]); ``` -!!! question "batchUpdateLinks" +## Remove link + +!!! abstract "removeLink" - Batch update infos of link-type columns + Delete the link to the record from table `linkedTableName` whose id is `linkedRowId` in the row from table `tableName` whose id is `rowId`. Every arguments are `String`. + + ```js + base.removeLink(linkId, tableName, linkedTableName, rowId, linkedRowId); + ``` + + __Output__ Nothing + + __Example__ ```js - base.batchUpdateLinks(link_id, table_id, other_table_id, row_id_list, other_rows_ids_map) + base.removeLink('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); ``` diff --git a/docs/scripts/javascript/objects/output.md b/docs/scripts/javascript/objects/output.md index 52c62e36..0a5ad4b4 100644 --- a/docs/scripts/javascript/objects/output.md +++ b/docs/scripts/javascript/objects/output.md @@ -1,15 +1,17 @@ # Output -Output object supports output strings in text or Markdown format. +Two functions are available to display results in the text editor window, allowing you to output strings in text or Markdown format. -!!! quote "text" +!!! abstract "text" - Prints the content of the passed variable as normal text. Code Syntax is ignored and just printed. + Prints the content of `anything` as normal text. Code Syntax is ignored and just printed. ``` js output.text(anything: String/Object/Array) ``` + __Output__ String + __Example__ ``` js @@ -17,14 +19,16 @@ Output object supports output strings in text or Markdown format. output.text(table.name); ``` -!!! quote "markdown" +!!! abstract "markdown" - Prints the content of the passed variable. Markdown formating is used to style the output. + Prints the content of `anything`, while using Markdown formatting to style the output. ``` js output.markdown(anything: String/Object/Array) ``` + __Output__ String + __Example__ ``` js diff --git a/docs/scripts/javascript/objects/rows.md b/docs/scripts/javascript/objects/rows.md index 78efa2fe..c5e127fb 100644 --- a/docs/scripts/javascript/objects/rows.md +++ b/docs/scripts/javascript/objects/rows.md @@ -1,77 +1,236 @@ # Rows -Interact with the rows of a SeaTable base. +You'll find below all the available methods to interact with the rows of a SeaTable table. In this section, you'll have to deal with the id of the rows. You can find few tips on how to get it in [the user manual](https://seatable.com/help/was-ist-die-zeilen-id/). -## Get rows +{% + include-markdown "includes.md" + start="" + end="" +%} -!!! question "getRows" +## Get row(s) - Get all the rows of the view and return an array. +!!! abstract "getRow / getRowById (deprecated)" + + Get a `table`'s row via its id `rowId`. ``` js - base.getRows(table: Object/String, view: Object/String); + base.getRow(table: Object/String /* (1)! */, rowId: String); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Single row object (throws an error if `table` doesn't exist or if no row with the specified id `rowId` exists) + + __Example__ ``` js const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'view1'); - const rows = base.getRows(table, view); + const row = base.getRow(table, "M_lSEOYYTeuKTaHCEOL7nw"); ``` ``` js - const rows = base.getRows('Table1', 'view1'); + const row = base.getRow('Table1', "M_lSEOYYTeuKTaHCEOL7nw"); ``` -!!! question "query" +!!! abstract "getRows" - Use sql to query a base. SQL-Query is the most powerful function to the data from a base. Most SQL-syntax is support. + Get all the rows displayed in the `view` of a `table`. ``` js - await base.query(sql) + base.getRows(table: Object/String, view: Object/String /* (1)! */); ``` - __Example: Get everything with a wildcard__ + 1. `table`: either a table object or the table name + + `view` (required): either a view object or the view name + + __Output__ Array of row objects (throws an error if `table` or `view` doesn't exist) + + __Example__ + + ``` js + const table = base.getTableByName('Table1'); + const view = base.getViewByName(table, 'Default View'); + const rows = base.getRows(table, view); + + rows.forEach((row) => { + output.text(row._id); + }) + ``` ``` js - const data = await base.query('select * from Bill') - output.text(data) // (1)! + const rows = base.getRows('Table1', 'Default View'); ``` - 1. Returns for example the following: +!!! abstract "query" + + Use SQL to query a base. SQL queries are the most powerful way access data stored in a base. If your not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). Most SQL syntax is supported, you can check the [SQL Reference](/scripts/sql/introduction.md) section of this manual for more information. + + ``` js + await/* (1)! */ base.query(sqlStatement: String); + ``` + + 1. `await` is used for asynchronous functions. This is **required** to ensure that the following operations (or the variable where you store the results) wait for the query's response to arrive before continuing to execute the script + + !!! info "Backticks for table or column names containing or special characters or using reserved words" + For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. + + Similarly, if some of your of table or column names are the same as [SQL function](/scripts/sql/functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. + + __Output__ Array of row objects (single empty object if no row match the request's conditions) + + All the examples below are related to a table **Bill** with the following structure/data: + + | name | price | year | + | ----- | ----- | ----- | + | Bob | 300 | 2021 | + | Bob | 300 | 2019 | + | Tom | 100 | 2019 | + | Tom | 100 | 2020 | + | Tom | 200 | 2021 | + | Jane | 200 | 2020 | + | Jane | 200 | 2021 | + + + __Example: Get everything with a wildcard__ + + === "Function call" + + ``` js + const data = await base.query('select * from Bill');/* (1)! */ + output.text(data); ``` + + 1. `*` means that you want to get the whole rows data (columns's values and specific row data such as id, etc.) + + === "Output" + + ```json [ - {"name":"Bob","price":"300","year":"2021"}, - {"name":"Bob","price":"300","year":"2019"}, - {"name":"Tom","price":"100","year":"2019"}, - {"name":"Tom","price":"100","year":"2020"}, - {"name":"Tom","price":"200","year":"2021"}, - {"name":"Jane","price":"200","year":"2020"}, - {"name":"Jane","price":"200","year":"2021"} + { + "name": "Bob", + "price": 300, + "year": 2021, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:19.106+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "W77uzH1cSXu2v2UtqA3xSw" + }, + { + "name": "Bob", + "price": 300, + "year": 2019, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:22.112+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "IxONgyDFQxmcDKpZWlQ9XA" + }, + { + "name": "Tom", + "price": 100, + "year": 2019, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:23.4+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "K4LBuQ7aSjK9JwN14ITqvA" + }, + { + "name": "Tom", + "price": 100, + "year": 2020, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "EHcQEaxiRzm3Zvq8B33bwQ" + }, + { + "name": "Tom", + "price": 200, + "year": 2021, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "CjaCdBlNRXKkYkm231shqg" + }, + { + "name": "Jane", + "price": 200, + "year": 2020, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "YzmUexIAR7iDWmhKGHgpMw" + }, + { + "name": "Jane", + "price": 200, + "year": 2021, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "HJi7wbUMQIOuIlPaoO9Fbg" + } ] ``` - __Example: WHERE__ - - ``` js - const data = await base.query('select name, price from Bill where year = 2021') - output.text(data) // (1)! + __Example with WHERE__ - const data = await base.query('select name, price from Bill where name = "Bob"') - output.text(data) // (2)! - ``` + === "Function call 1 (filter by year)" - 1. Returns for example the following: + ``` js + const data = await base.query('select name, price from Bill where year = 2021'); + output.text(data); ``` + + === "Output #1" + + ```json [ {"name":"Bob","price":"300"}, {"name":"Tom","price":"200"}, {"name":"Jane","price":"200"} ] ``` - 2. Returns for example the following: + + === "Function call 2 (filter by name)" + + ```js + const data = await base.query('select name, price, year from Bill where name = "Bob"'); + output.text(data); ``` + + === "Output #2" + + ```json [ {"name":"Bob","price":"300","year":"2021"}, {"name":"Bob","price":"300","year":"2019"} @@ -79,109 +238,247 @@ Interact with the rows of a SeaTable base. ``` - __Example: GROUP BY__ + __Example with GROUP BY__ - ``` js - const data = await base.query('select name, sum(price) from Bill group by name') - output.text(data) // (1)! - ``` + === "Function call" - 1. Returns for example the following: + ``` js + const data = await base.query('select name, sum(price) from Bill group by name'); + output.text(data); ``` + + === "Output" + + ```json [ - {'SUM(price)': 600, 'name': 'Bob'}, - {'SUM(price)': 400, 'name': 'Tom'}, - {'SUM(price)': 400, 'name': 'Jane'} + {'name': 'Bob', 'SUM(price)': 600}, + {'name': 'Tom', 'SUM(price)': 400}, + {'name': 'Jane', 'SUM(price)': 400} ] ``` - __Example: DISTINCT__ + __Example with DISTINCT__ - ``` js - const data = await base.query('select distinct name from Bill') - output.text(data) // (1)! - ``` + === "Function call" - 1. Returns for example the following: + ``` js + const data = await base.query('select distinct name from Bill'); + output.text(data); ``` + + === "Output" + + ```json [ - {'SUM(price)': 600, 'name': 'Bob'}, - {'SUM(price)': 400, 'name': 'Tom'}, - {'SUM(price)': 400, 'name': 'Jane'} + {'name': 'Bob'}, + {'name': 'Tom'}, + {'name': 'Jane'} ] ``` -!!! question "getGroupedRows" - - Get rows in the grouped view. - - ``` js - base.getGroupedRows(table: Object/String, view: Object/String); - ``` - - __Example__ +!!! abstract "getGroupedRows" - ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'GroupedView'); - const groupViewRows = base.getGroupedRows(table, view); - ``` + Get rows in the grouped `view` of a `table`. ``` js - const groupViewRows = base.getGroupedRows('Table1', 'GroupedView'); + base.getGroupedRows(table: Object/String, view: Object/String /* (1)! */); ``` -!!! question "getRow / getRowById (deprecated)" + 1. `table`: either a table object or the table name + + `view` (required): either a view object or the view name - Get a `row` via its `id` and return a row object. + __Output__ Array of grouped rows object (see Output example below) - ``` js - base.getRow(table: Object/String, rowId: String); - ``` + __Example__ - __Examples__ + === "Function call" - ``` js - const table = base.getTableByName('Table1'); - const row = base.getRow(table, "M_lSEOYYTeuKTaHCEOL7nw"); - ``` + ``` js + const table = base.getTableByName('Table1'); + const view = base.getViewByName(table, 'GroupedView'); + const groupViewRows = base.getGroupedRows(table, view); + ``` + + === "Output example" - ``` js - const row = base.getRow('Table1', "M_lSEOYYTeuKTaHCEOL7nw"); - ``` + ```json + [ + { /* (1)! */ + "column_name": "date", + "column_key": "tc2B", + "cell_value": "2025-09", + "rows": [], /* (2)! */ + "subgroups": [ + { + "column_name": "Val2", + "column_key": "7Q0G", + "cell_value": 462, + "rows": [ + { + "bjcM": 12, + "0000": "John", + "7Q0G": 462, + "tc2B": "2025-09-11", + "Tm99": "520035", + "_creator": "aa", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_id": "AGO_2SiiTY61uMr-tTVGvQ", + "_ctime": "2025-09-11T07:38:23.082+00:00", + "_mtime": "2025-09-11T09:28:32.204+00:00", + "mpxK": 0 + }, + { + "bjcM": 12, + "0000": "John", + "7Q0G": 462, + "tc2B": "2025-09-11", + "Tm99": "520035", + "_creator": "aa", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_id": "WTu6o6lxS-ChnamkU1wjuA", + "_ctime": "2025-09-11T07:39:10.297+00:00", + "_mtime": "2025-09-11T09:28:32.204+00:00", + "mpxK": 0 + } + ], + "subgroups": [] /* (3)! */ + } + ] + }, + { + "column_name": "date", + "column_key": "tc2B", + "cell_value": null, + "rows": [], + "subgroups": [ + { + "column_name": "Val2", + "column_key": "7Q0G", + "cell_value": 4, + "rows": [ + { + "_id": "GIgxrz8VSzm-aHSbJ6_i4w", + "_participants": [], + "_creator": "cc7a1d0fce...b65b99@auth.local", + "_ctime": "2025-09-03T07:03:57.838+00:00", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_mtime": "2025-09-17T15:31:04.150+00:00", + "bjcM": 1, + "0000": "name", + "7Q0G": 4, + "plxx": 5676, + "Tm99": "207110", + "mpxK": "" + }, + { + "_id": "PSfpr9dzRPaKUeIn-3va0w", + "_participants": [], + "_creator": "cc7a1d0fce...b65b99@auth.local", + "_ctime": "2025-09-03T07:03:57.838+00:00", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_mtime": "2025-09-11T09:28:32.204+00:00", + "bjcM": 0, + "0000": "zu", + "7Q0G": 4, + "plxx": 3872, + "Tm99": "375528", + "mpxK": 0 + } + ], + "subgroups": [] + }, + { + "column_name": "Val2", + "column_key": "7Q0G", + "cell_value": 9, + "rows": [ + { + "_id": "H3djeRnkQdWhKBhEG2cGUw", + "_participants": [], + "_creator": "cc7a1d0fce...b65b99@auth.local", + "_ctime": "2025-09-03T07:03:57.838+00:00", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_mtime": "2025-09-11T09:28:32.204+00:00", + "bjcM": 3, + "0000": "a", + "7Q0G": 9, + "plxx": 1668, + "Tm99": "520035", + "mpxK": 0 + }, + { + "_id": "ARedNyn8R7CZFmRushZmvQ", + "_participants": [], + "_creator": "cc7a1d0fce...b65b99@auth.local", + "_ctime": "2025-09-03T08:23:03.776+00:00", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_mtime": "2025-09-17T15:31:09.842+00:00", + "0000": "b", + "bjcM": "", + "7Q0G": 9, + "plxx": 610, + "Tm99": "211464", + "mpxK": 0 + }, + { + "_id": "L4IWGz4hT3qb1_u9bBbvFg", + "_participants": [], + "_creator": "cc7a1d0fce...b65b99@auth.local", + "_ctime": "2025-09-03T14:03:51.524+00:00", + "_last_modifier": "cc7a1d0fce...b65b99@auth.local", + "_mtime": "2025-09-17T15:31:08.429+00:00", + "0000": "name", + "bjcM": 15, + "7Q0G": 9, + "plxx": 565, + "Tm99": "745764", + "mpxK": 0 + } + ], + "subgroups": [] + } + ] + } + ] + ``` -## Delete row + 1. Grouped rows object containing either `rows` or `subgroups` (array of grouped rows objects) in the case of multiple grouping rules -!!! question "deleteRow / deleteRowById (deprecated)" + 2. No `rows`: this grouped rows object only contains `subgroups` (member of the first grouping rule) - Delete a `row` in a table by its `id`. + 3. No `subgroups`: this grouped rows object only contains `rows` (member of the last grouping rule) ``` js - base.deleteRow(table: Object/String, rowId: String); + const groupViewRows = base.getGroupedRows('Table1', 'GroupedView'); ``` - __Examples__ +## Add row - ``` js - const table = base.getTableByName('Table1'); - base.deleteRow(table, 'M_lSEOYYTeuKTaHCEOL7nw'); - ``` +!!! abstract "appendRow / addRow (deprecated)" + + Add a row to a `table`. This row contains the data specified in the object `rowData`. The row will be empty if `rowData` is empty or if it contains only keys that don't exist in the `table`. ``` js - base.deleteRow('Table1', 'M_lSEOYYTeuKTaHCEOL7nw'); + base.appendRow(table: Object/String, rowData: Object, viewName: String /* (1)! */) ``` -## Add row + 1. `table`: either a table object or the table name -!!! question "appendRow / addRow(deprecated)" + `rowData`: object (pairs of `key`:`value`, each `key` being the name of a column), for example: - Add a row to a table. + ``` + { + 'First Name': 'John', + 'Last Name': 'Doe', + 'Invoice amount': 100, + 'Products': ['Office Supplies', 'Computer'] + } + ``` - ``` js - base.appendRow(table: Object/String, rowData: Object, viewName?: String) - ``` + __Output__ Single row object (throws an error if `table` doesn't exist) - __Examples__ + __Example__ ``` js const table = base.getTableByName('Table1'); @@ -189,26 +486,27 @@ Interact with the rows of a SeaTable base. base.appendRow(table, {'Name': 'Alex', 'Age': '18'}, 'Default View'); ``` - ``` js - base.addRow('Table1', {'Name': 'Alex', 'Age': '18'}); - base.addRow('Table1', {'Name': 'Alex', 'Age': '18'}, 'Default View'); - ``` - ## Update row(s) -!!! question "updateRow / modifyRow(deprecated)" +!!! abstract "updateRow / modifyRow(deprecated)" - Modify a row in the table. + Modify a `row` in the `table`. The `updateRowData` object (pairs of `key`:`value`, each `key` being the name of a column) need to contain only the data you want to update. To reset a value, specify the `key`:`value` pair with an empty string `''`. ``` js - base.updateRow(table: Object/String, row: Object/string, updateRowData: Object); + base.updateRow(table: Object/String, row: Object/String, updateRowData: Object /* (1)! */); ``` - __Examples__ + 1. `table`: either a table object or the table name + + `row`: either a row object or the row id + + __Output__ Nothing (throws an error if `table` doesn't exist or if no row with the specified id exists) + + __Example__ ``` js const table = base.getTableByName('Table1'); - const row = base.getRowById(table, "M_lSEOYYTeuKTaHCEOL7nw"); + const row = base.getRow(table, "M_lSEOYYTeuKTaHCEOL7nw"); base.updateRow(table, row, {'Name': 'new name', 'number': 100}); ``` @@ -216,254 +514,376 @@ Interact with the rows of a SeaTable base. base.updateRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw', {'Name': 'new name', 'number': 100}) ``` -!!! question "modifyRows" +!!! abstract "modifyRows" - Modify multiple rows in the table at once. + Modify multiple `rows` in the `table` at once. `updatedRows` is an array of `updateRowData` objects (see above). Please note that `rows` only accepts an array of row objects (and not of ids). ``` js - base.modifyRow(table: Object/String, rows: Array, updatedRows: Array); + base.modifyRows(table: Object/String, rows: Array of Object, updatedRows: Array of Object /* (1)! */); ``` + 1. `table`: either a table object or the table name + + `rows`: array of row objects **only** (not row ids) + + __Output__ Nothing (throws an error if `table` doesn't exist or if one row in `rows` doesn't exists) + __Example__ ``` js const table = base.getTableByName('Table1'); - const rows = base.getRows('Table1', 'Default view'); + const rows = base.getRows(table, 'Default View'); const selectedColumnName = 'Name'; const selectedRows = [], updatedRows = []; rows.forEach((row) => { - if (row[columnName] === 'name') { + if (row[selectedColumnName] === 'name') { selectedRows.push(row); - updatedRows.push({columnName: 'name1'}); + updatedRows.push({[selectedColumnName]: 'name1'}); } }); - base.modifyRow(table, selectedRows, updatedRows); + base.modifyRows(table, selectedRows, updatedRows); ``` -## Filter + ``` js + base.modifyRows('Table1', [base.getRow('Table1','GIgxrz8VSzm-aHSbJ6_i4w'),base.getRow('Table1','PSfpr9dzRPaKUeIn-3va0w')], [{'Name': 'name'},{'Name': 'name'}]); + ``` + +## Delete row -`base.filter` allows to pass a conditional statement. It filters the rows that meet the conditions in the table, and returns a querySet object. +!!! abstract "deleteRow / deleteRowById (deprecated)" -!!! question "filter" + Delete a row in a `table` by its id `rowId`. ``` js - base.filter(tableName, viewName, filterExpression) + base.deleteRow(table: Object/String, rowId: String /* (1)! */); ``` + 1. `table`: either a table object or the table name + + `rowId`: the id of the row to delete + + + __Output__ Nothing (no error if no row with id `rowId` exists) + __Example__ ``` js - // Filter out rows whose number column is equal to 5, and return a querySet object - const querySet = base.filter('Table1', 'Default', 'number = 5'); + const table = base.getTableByName('Table1'); + base.deleteRow(table, 'M_lSEOYYTeuKTaHCEOL7nw'); + ``` + + ``` js + base.deleteRow('Table1', 'M_lSEOYYTeuKTaHCEOL7nw'); + ``` + + + +## Filter + +!!! abstract "filter" + + Filters the rows displayed in the view `viewName` of the `table` that meet the conditions of the `filterExpression` (conditional statement), and returns a querySet object. See the `filterExpression` reference below for more details. + + ``` js + base.filter(tableName: String, viewName: String, filterExpression: String); + ``` + + __Output__ Single querySet object (see below), the `rows` array being empty if no row meet the `filterExpression` conditions + + __Example__ + + === "Function call" + + ``` js + // Filter out rows whose number column is equal to 5, and return a querySet object + const querySet = base.filter('Table1', 'Default View', 'number = 5'); + ``` + + === "Output structure" + + ```json + { + "rows": [ /* (1)! */ + ... + ], + "table": { /* (2)! */ + ... + }, + "parser": { + ... + } + } + ``` + + 1. `rows`: array of the rows in the view `viewName` meeting the `filterExpression` conditions + + 2. `table`: the whole `table` object + + + ```js + const querySet = base.filter("Table1", "Default View", "age>18"/* (1)! */) ``` -### Filter Expressions + 1. `age`: column name + + `>`: operator + + `18`: parameter -!!! question "filter expressions" +### filterExpression reference - The table query will become simpler and more efficiency by using the sql-like statements as a paramter in `base.filter()` function. In different column types, there are a little differences in the query method and the format of input statement. These are the available __query methods__: +!!! abstract "filterExpression" - * **greater-less query:** >, >, =, \<, \<= - * **equal-unequal query:** =, \<> - * **computation:** +, -, *, /, ^, % + The most common operators are available to define the conditional statement of the `filterExpression`: - Here is an example based on the code `queryset = base.filter("Table1", "age>18")` + | Type of operators | Available operators | + | ------------------ | ------------------- | + | Greater-Less comparisons | >=, >, <, <= | + | Equal-Not equal comparisons | =, <> (not equal to) | + | Arithmetic operators | +, -, *, /, ^ (power), % (modulo) | + | Logical operators | and, or | + + Depending on the data type, there are slight differences in the query method and the format of input statement. Here is a list of the possible operations for each type: - * age: column name - * \>: operator - * 18: parameter - | Data structure | Column type | Format of greater-less query | Format of equal-unequal query | computation | + | Data structure | Column type | Format for Greater-Less comparisons | Format for Equal-Not equal comparisons | Arithmetic operators | | -------------- | ----------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------- | :---------- | | String | Text, Long Text, URL,Email, Single Select | Unsupported | String | Unsupported | | List | Multiple Select | Unsupported | String | Unsupported | - | Number | Number | int, float | int, float, and empty string "" | Supported | + | Number | Number | Number | Number and empty String `""`"" | Supported | | Date | Date, Created time, Last modified time | Patterns: YYYY-MM-DD, YYYY-MM-DD hh:mm, YYYY-MM-DD hh\:mm:ss | Same patterns as greater-less query | Unsupported | - | Boolean | Checkbox | Unsupported | true, false and empty string "", (case-insensitive) | Unsupported | + | Boolean | Checkbox | Unsupported | true, false and empty String `""`, (case-insensitive) | Unsupported | - --- + + !!! info "Mind the quotes!" + For queries involving string-based or date-based columns, you'll have to use double quotes `" "` to define the `filterExpression` as you'll need simple quotes `' '` for the strings/dates... Or the opposite: use either `"column_name='hello world'"` or `'column_name="hello world"'` Here are more examples of the different filter expressions pending of the column type. - __String-based Column__ + __String-based Column__ (**Text, Long Text, URL, Email, Single Select** columns) - Column types include **Text, Long Text, URL, Email, Checkbox**. - ```python - # 1. equal-unequal query - base.filter('Table1', 'view_name', "column_name=hello world") - base.filter('Table1', 'view_name', "column_name!=''") + ```js + // Equal-unequal query + base.filter('Table1', 'Default View', "column_name='hello world'") + base.filter('Table1', 'Default View', "column_name!=''") ``` - __List-based Column__ - - Column types include **Multiple Select** +
+ __List-based Column__ (**Multiple Select** columns) - ```python - # equal-unequal query - base.filter('Table1','view_name', "column_name=A and column_name=B") # Find the rows which contains both 'A' and 'B' + ```js + // Equal-unequal query + base.filter('Table1','Default View', "column_name='A' and column_name='B'") /* (1)! */ ``` - __Number-based Column__ + 1. Find the rows which contains both 'A' and 'B' - 1. Column types include **Number** +
+ __Number-based Column__ (**Number** columns) - ```javascript - # 1. greater-less query - base.filter('Table1', 'view_name', "column_name>18") - base.filter('Table1', 'view_name', "column_name>-10 and column_name<=0") + === "Greater-less query" - # 2. equal-unequal query - base.filter('Table1', 'view_name',"column_name<>20") - base.filter('Table1', 'view_name', "column_name=0") - base.filter('Table1', 'view_name',"column_name=''") + ```js + base.filter('Table1', 'Default View', "column_name>18") + base.filter('Table1', 'Default View', "column_name>-10 and column_name<=0") + ``` - ``` + === "Equal-unequal query" - 2. Computation + ```js + base.filter('Table1', 'Default View',"column_name<>20") + base.filter('Table1', 'Default View', "column_name=0") + base.filter('Table1', 'Default View',"column_name=''") + ``` - ```javascript - base.filter('Table1', 'view_name', "column_name+3>18") - base.filter('Table1', 'view_name', "column_name*2=18") - base.filter('Table1', 'view_name', "column_name-2=18") - base.filter('Table1', 'view_name', "column_name/2=18") - base.filter('Table1', 'view_name', "column_name^2=18") - base.filter('Table1', 'view_name', "column_name%2=1") - ``` + === "Arithmetic query" - __Date-based Column__ + ```js + base.filter('Table1', 'Default View', "column_name+3>18") + base.filter('Table1', 'Default View', "column_name*2=18") + base.filter('Table1', 'Default View', "column_name-2=18") + base.filter('Table1', 'Default View', "column_name/2=18") + base.filter('Table1', 'Default View', "column_name^2=18") + base.filter('Table1', 'Default View', "column_name%2=1") + ``` - Column types include **Date, Created time, Last modified time** +
+ __Date-based Column__ (**Date, Created time, Last modified time** columns) - ```javascript - # 1. greater-less query - base.filter('Table1', 'view_name', "column_name>'2020-1-30'") - base.filter('Table1', 'view_name', "column_name>='2019-1-1 5:30' and column_name<='2019-5-1 6:00'") + === "Greater-less query" - # 2. equal-unequal query - base.filter('Table1', 'view_name', "column_name='2020-1-1 10:59:59'") - base.filter('Table1', 'view_name', "column_name!=''") + ```js + base.filter('Table1', 'Default View', "column_name>'2020-1-30'") + base.filter('Table1', 'Default View', "column_name>='2019-1-1 5:30' and column_name<='2019-5-1 6:00'") + ``` - ``` + === "Equal-unequal query" - !!! note "Note that please use the quotes "" when making the date-time query" + ```js + base.filter('Table1', 'Default View', "column_name='2020-1-1 10:59:59'") + base.filter('Table1', 'Default View', "column_name!=''") + ``` - __Boolean-based Column__ +
+ __Boolean-based Column__ (**Checkbox** columns) - Column types include **Checkbox** + === "Equal-unequal query" - ```javascript - # equal-unequal query - base.filter('Table1', 'view_name','column_name=False') # Same as base.filter('Table1', "column_name=''") - base.filter('Table1', 'view_name', "column_name=True") + ```js + base.filter('Table1', 'Default View','column_name=False')/* (1)! */ + base.filter('Table1', 'Default View', "column_name=True") + ``` - ``` + 1. same as `base.filter('Table1', "column_name=''")` -### Filter Queries +### querySet handling -The return value of the `base.filter` function, this object provides some methods to simplify the operation of the filtered data +The output of the `base.filter` function is a `querySet` object. Here are the methods of this object provided to simplify the operations on the filtered data. -!!! question "filter" +!!! abstract "all" - Pass a conditional statement, filter out the rows that meet the conditions in the table, and return a querySet object. + Returns all filtered rows of the `querySet` in the form of a list. ``` js - base.filter(table: Object/String, ??, condition: ??) + querySet.all(); ``` + __Output__ Array of row objects + __Example__ ``` js - // Filter out rows whose number column is equal to 5, and return a querySet object - const querySet = base.filter('Table1', 'Default', 'number = 5'); + const querySet = base.filter('Table1', 'Default View', 'number = 5'); + const list = querySet.all(); + output.text(list); ``` -!!! tip "all" +!!! abstract "count" - Returns all filtered data in the form of a list + Returns the number of filtered rows of the `querySet`. ``` js - querySet.all(linkId, tableName, linkedTableName, rowId, updatedlinkedRowIds) + querySet.count(); ``` + __Output__ Number + __Example__ - ``` js - const list = querySet.all(); + ```js + const querySet = base.filter('Table1', 'Default View', 'number = 5'); + const count = querySet.count(); + output.text(`The querySet contains ${count} rows`); ``` -!!! tip "count" +!!! abstract "first" - Returns the number of filtered rows + Return the first filtered row of the `querySet`. + + ``` js + querySet.first(); + ``` + + __Output__ Single row object (`undefined` if the `querySet` contains no row) __Example__ ```js - const count = querySet.count(); + const querySet = base.filter('Table1', 'Default View', 'number = 5'); + const row = querySet.first(); ``` -!!! tip "last" +!!! abstract "last" - Return the last filtered data + Return the last filtered row of the `querySet`. + + ``` js + querySet.last(); + ``` + + __Output__ Single row object (`undefined` if the `querySet` contains no row) __Example__ ```js + const querySet = base.filter('Table1', 'Default View', 'number = 5'); const row = querySet.last(); ``` -!!! tip "first" - - Return the first filtered data +!!! abstract "delete" - __Example__ + Delete all filtered rows of the `querySet` and return the number of rows successfully deleted. - ```js - const row = querySet.first(); + ``` js + querySet.delete(); ``` -!!! tip "delete" - - Delete all filtered rows and return the number of successfully deleted + __Output__ Number __Example__ ```js + const querySet = base.filter('Table1', 'Default View', 'number = 5'); const count = querySet.delete(); + output.text(`${count} rows successfully deleted!`); ``` -!!! tip "update" +!!! abstract "update" - Modify the row data and return the updated data + Modify the row data according to the`rowData` Object and return the updated rows. + + ``` js + querySet.update(rowData: Object/* (1)! */); + ``` + + 1. `rowData`: object (pairs of `key`:`value`, each `key` being the name of a column) + + __Output__ Array of row objects (empty Array if no filtered row) __Example__ ```js - // Modify the contents of the Name column of all filtered rows to xxxx + // Modify the content of the Name column of all filtered rows to xxxx + const querySet = base.filter('Table1', 'Default View', 'number = 5'); const rows = querySet.update({Name: 'xxxx'}); ``` -!!! tip "filter" +!!! abstract "filter" - Further filtering, return a querySet object + Further filtering using the `filterExpression` conditional statement. + + ```js + querySet.filter(filterExpression: String); + ``` + + __Output__ Single querySet object __Example__ ```js - // Filter out the rows with the value of Tom in the Name column of the querySe - const querySet1 = querySet.filter('Name = "Tom"'); + // Filter out the rows with the value of Tom in the Name column of querySet1 + const querySet1 = base.filter('Table1', 'Default View', 'number = 5'); + const querySet2 = querySet1.filter("Name = 'Tom'"); ``` -!!! tip "get" +!!! abstract "get" + + Return the first row of the querySet that meets the conditions of the new `filterExpression`. This is equivalent to `querySet.filter(filterExpression).first()` + + ```js + querySet.get(filterExpression: String); + ``` - Get a piece of data in the querySet that meets the conditions, and return a row + __Output__ Single row object (`undefined` if no row meets the conditions of the `filterExpression`, `#ERROR!` if the `filterExpression` is wrong) __Example__ ```js // Get the first data of Tom in the Name column of the querySet - const row = querySet.get('Name = "Tom"'); + const querySet = base.filter('Table1', 'Default View', 'number = 5'); + const row = querySet.get("Name = 'Tom'"); ``` diff --git a/docs/scripts/javascript/objects/tables.md b/docs/scripts/javascript/objects/tables.md index 19049a93..05a823b2 100644 --- a/docs/scripts/javascript/objects/tables.md +++ b/docs/scripts/javascript/objects/tables.md @@ -1,16 +1,25 @@ # Tables -All available functions to interact with the tables of a SeaTable base. +You'll find below all the available methods to interact with the tables of a SeaTable base. + +{% + include-markdown "includes.md" + start="" + end="" +%} + +You can have a look at the specific [view](./views.md#global-structure), [column](./columns.md#global-structure) or [row](./rows.md#global-structure) structure on the corresponding pages. ## Get Table(s) -!!! question "getActiveTable" +!!! abstract "getActiveTable" - Get the currently selected table and return a table object. + Get the currently selected table. ``` js base.getActiveTable(); ``` + __Output__ Single table object __Example__ ``` js @@ -18,13 +27,14 @@ All available functions to interact with the tables of a SeaTable base. output.text(`The name of the active table is: ${table.name}`); ``` -!!! question "getTables" +!!! abstract "getTables" - Get all tables of this base as `json` object with all rows and metadata. + Get all tables of the current base. ``` base.getTables(); ``` + __Output__ Array of table objects __Example__ ``` js @@ -32,28 +42,36 @@ All available functions to interact with the tables of a SeaTable base. output.text(tables); ``` -!!! question "getTableByName" +!!! abstract "getTableByName" + + Get a table object by its name. - Get a table object by its name. The object contains all rows and metadata. ``` js base.getTableByName(tableName: String); ``` + __Output__ Single table object (`undefined` if table doesn't exist) + __Example__ - ``` js + + ```js const table = base.getTableByName('Table1'); + // Display only table _id output.text(`The id of the table is: ${table._id}`); + // Display whole table structure + output.text(table); ``` ## Add Table -!!! question "addTable" +!!! abstract "addTable" - Add a new table to this base. The table should not exist already in your base. + Add a new table to this base, given the new table name `tableName`. Please ensure that you choose a `tableName` that doesn't already exists in your base. ``` js base.addTable(tableName: String); ``` + __Output__ Nothing __Example__ ``` js @@ -63,14 +81,16 @@ All available functions to interact with the tables of a SeaTable base. ## Rename Table -!!! question "renameTable" +!!! abstract "renameTable" - Rename an existing table. + Rename an existing table named `oldName` to `newName`. Please ensure that you choose a `newName` that doesn't already exists in your base. ``` js base.renameTable(oldName: String, newName: String); ``` + __Output__ Nothing (throws an error if no table named `oldName` exists) + __Example__ ``` js const old_name = "Table1"; @@ -81,13 +101,14 @@ All available functions to interact with the tables of a SeaTable base. ## Delete Table -!!! question "deleteTable" +!!! abstract "deleteTable" - Delete a table from the base. By the way, the table can be [restored from the logs](https://seatable.com/help/eine-geloeschte-tabelle-wiederherstellen/). + Delete a table named `tableName` from the base. By the way, the table can be [restored from the logs](https://seatable.com/help/eine-geloeschte-tabelle-wiederherstellen/). Deleting the last table is not possible. ``` js base.deleteTable(tableName: String); ``` + __Output__ Nothing (throws an error if no table named `tableName` exists) __Example__ ``` js diff --git a/docs/scripts/javascript/objects/utilities.md b/docs/scripts/javascript/objects/utilities.md index cdb90c6f..11046c64 100644 --- a/docs/scripts/javascript/objects/utilities.md +++ b/docs/scripts/javascript/objects/utilities.md @@ -4,14 +4,16 @@ Utility functions help you to work with data in SeaTable. ## Date and Time -!!! success "formatDate" +!!! abstract "formatDate" - Format date to 'YYYY-MM-DD' to be used in a date column. + Format `date` to 'YYYY-MM-DD' to be used in a date-type column. ``` js - base.utils.formatDate(date: date object) + base.utils.formatDate(date: Date Object) ``` + __Output__ String + __Example__ ``` js let date = new Date(); @@ -19,14 +21,16 @@ Utility functions help you to work with data in SeaTable. output.text(formatDate); ``` -!!! success "formatDateWithMinutes" +!!! abstract "formatDateWithMinutes" - Format date to 'YYYY-MM-DD HH:mm' to be used in a date column.. + Format `date` to 'YYYY-MM-DD HH:mm' to be used in a date-type column. ``` js base.utils.formatDateWithMinutes(date: date object) ``` + __Output__ String + __Example__ ``` js let date = new Date(); @@ -36,40 +40,71 @@ Utility functions help you to work with data in SeaTable. ## Lookup and Query -!!! success "lookupAndCopy" +!!! abstract "lookupAndCopy" + + Similar to the Microsoft Excel VLOOKUP function. Find a matching row in the *source* table for each row of the *target* table, and then copy the data of the specified cell of the matching row to the specified cell of the *target* row. Every arguments are `String`. + + ``` js + base.utils.lookupAndCopy(targetTable, targetColumn, targetColumnToCompare, sourceTableName, + sourceColumnName, sourceColumnToCompare = null /* (1)! */); + ``` + + 1. `targetTable`: the name of the *target* table - i.e. the table you want to copy data **into** + + `targetColumn`: the column of `targetTable` you want to copy data into + + `targetColumnToCompare`: the column of `targetTable` you want to compare to a column of table `sourceTableName` - Similar to the vlookup function in Excel. Find a matching row in the source table for each row of the target table, and then copy the data of the specified cell of the matching row to the specified cell of the target row. + `sourceTableName`: the *source* table - i.e. the table you want to copy data **from** - | Name | Email | + `sourceColumnName`: the column of `sourceTableName` you want to copy data from + + `sourceColumnToCompare`: If specified, the column of `sourceTableName` you want to compare with `targetColumnToCompare` to find matching rows. If not specified, the system will look for a column with the name `targetColumn` in the table `sourceTableName` + + __Output__ Nothing (throws an error if some tables or columns do not exist) + + __Principle example__ + + Here are two tables, the *source* table containing both names and emails for few Avengers whereas the *target* table only has the user names. + + **Source table** + + | Name | SourceEmail | | --- | --- | | Hulk | greenbigboy@stark-industries.movie | | Tony | ironman|stark-industries.movie | - The target table only has the user names but we want to copy the email address from the source table to the target table, then this function can be used. + **Target table** - | Name | Email | + | Name | TargetEmail | | --- | --- | | Hulk | | | Tony | | - ``` js - base.utils.lookupAndCopy(targetTable, targetColumn, targetColumnToCompare, sourceTableName, sourceColumnName, sourceColumnToCompare = null); + To copy the email addresses from the *source* table to the *target* table, this function can be used with the following syntax: + + ``` + base.utils.lookupAndCopy('Target table', 'TargetEmail', 'Name', 'Source table', 'SourceEmail'); ``` __Example__ ``` js - // Match the rows with the same content in the Name column of Table1 and Table2, copy the contents of the Email column of the row in Table1 to the Email column of the corresponding row in Table2 - base.utils.lookupAndCopy('Table2', 'Email', 'Name', 'Table1', 'Name'); - - // Match the rows with the same content in the Name column in Table1 and the Name1 column in Table2, and copy the contents of the Email column of the row in Table1 to the Email1 column of the corresponding row in Table2 + // Match the rows with the same content in the Name column of Table1 and Table2, + // copy the contents of the Email column of the row in Table1 to the Email column + // of the corresponding row in Table2 + base.utils.lookupAndCopy('Table2', 'Email', 'Name', 'Table1', 'Email'); + + // Match the rows with the same content in the Name column in Table1 and the Name1 column + // in Table2, and copy the contents of the Email column of the row in Table1 to the + // Email1 column of the corresponding row in Table2 base.utils.lookupAndCopy('Table2', 'Email1', 'Name1', 'Table1', 'Email', 'Name'); ``` -!!! success "query" +!!! abstract "query" - Filter and summary the table data by SQL like statements. + Filter and summarize the table `tableName` data of the view `viewName` by SQL like `query` statements. ``` js base.utils.query(tableName: String, viewName: String, query: String); @@ -78,6 +113,8 @@ Utility functions help you to work with data in SeaTable. __Example__ ``` js - // Filter out the rows where the sum of the three columns 'number', 'number1', and 'number2' is greater than 5 then sum the number and number2 columns in these rows, return {number: 12, number2: 23} - base.utils.query('Table1', 'View_name', 'select sum(number), sum(number2) where number + number1 + number2 > 5'); + // Filter out the rows where the sum of the three columns 'number', 'number1', + // and 'number2' is greater than 5 then sum the number and number2 columns in these rows, + // return {number: 12, number2: 23} + base.utils.query('Table1', 'Default View', 'select sum(number), sum(number2) where number + number1 + number2 > 5'); ``` diff --git a/docs/scripts/javascript/objects/views.md b/docs/scripts/javascript/objects/views.md index 3d390755..75bef272 100644 --- a/docs/scripts/javascript/objects/views.md +++ b/docs/scripts/javascript/objects/views.md @@ -1,17 +1,25 @@ # Views -Functions to interact with the views of a table. +You'll find below all the available methods to interact with the views of a SeaTable table. -## Get Views +{% + include-markdown "includes.md" + start="" + end="" +%} -!!! question "getActiveView" +## Get View(s) - Get the current view, the method return a view object. +!!! abstract "getActiveView" + + Get the current view of the active table. ``` js base.getActiveView(); ``` + __Output__ Single view object + __Example__ ``` js const view = base.getActiveView(); @@ -19,52 +27,64 @@ Functions to interact with the views of a table. output.text(view); ``` -!!! question "listViews / getViews (deprecated)" +!!! abstract "getViewByName" - Get all the views of the current table, and return all the views in an array + Get a view of a particular `table`, specified by its name `viewName`. ``` js - base.listViews(table: Object/String); + base.getViewByName(table: Object/String/* (1)! */, viewName: String); ``` + 1. `table`: either a table object or the table name + + __Output__ Single view object (`undefined` if no view called `viewName` exists, throws an error if `table` doesn't exist) + __Example__ ``` js const table = base.getTableByName('Table1'); - const views = base.listViews(table); - output.text(views.length); + const view = base.getViewByName(table, 'Default View'); + output.text(view.name); ``` -!!! question "getViewByName" - - Get a view object via its name, and return a view object. - ``` js - base.getViewByName(table: Object/String, viewName: String); + const view = base.getViewByName('Table1', 'Default View'); + output.text(view.name); ``` - __Examples__ +!!! abstract "listViews / getViews (deprecated)" + + Get all the views of the `table`. + ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'view 1'); - output.text(view.name); + base.listViews(table: Object/String/* (1)! */); ``` + 1. `table`: either a table object or the table name + + __Output__ Array of view objects (throws an error if `table` doesn't exist) + + __Example__ ``` js - const view = base.getViewByName('Table1', 'view 1'); - output.text(view.name); + const table = base.getTableByName('Table1'); + const views = base.listViews(table); + output.text(views.length); ``` ## Add View -!!! question "addView" +!!! abstract "addView" - Add a view to a table. + Add a view named `viewName` to a `table`. ``` js - base.addView(table: Object/String, viewName: String); + base.addView(table: Object/String/* (1)! */, viewName: String); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Nothing (throws an error if `table` doesn't exist) + + __Example__ ``` js const table = base.getTableByName('Table1'); base.addView(table, 'view 2'); @@ -76,35 +96,43 @@ Functions to interact with the views of a table. ## Rename View -!!! question "renameView" +!!! abstract "renameView" - Rename a view in the table. + Rename a view in the `table` specified by its current name `currentViewName` and its new name `nextViewName`. Please ensure that you choose a `nextViewName` that doesn't already exists in your `table`. ``` js - base.renameView(table: Object/String, currentViewName: String, nextViewName: String); + base.renameView(table: Object/String/* (1)! */, currentViewName: String, nextViewName: String); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Nothing (throws an error if `table` or `currentViewName` doesn't exist) + + __Example__ ``` js const table = base.getTableByName('Table1'); - base.renameView(table, 'view1', 'view2'); + base.renameView(table, 'Default View', 'view2'); ``` ``` js - base.renameView('Table1', 'view1', 'view2'); + base.renameView('Table1', 'Default View', 'view2'); ``` ## Delete View -!!! question "deleteView" +!!! abstract "deleteView" - Delete a view. + Delete a view in a particular `table`, specified by its name `viewName`. Deleting the last view is not possible. ``` js - base.deleteView(table: Object/String, viewName: String); + base.deleteView(table: Object/String/* (1)! */, viewName: String); ``` - __Examples__ + 1. `table`: either a table object or the table name + + __Output__ Nothing (throws an error if `table` doesn't exist or no view called `viewName` exists) + + __Example__ ``` js const table = base.getTableByName('Table1'); base.deleteView(table, 'view2'); diff --git a/docs/scripts/python/authorization_python.md b/docs/scripts/python/authorization_python.md deleted file mode 100644 index 863f5309..00000000 --- a/docs/scripts/python/authorization_python.md +++ /dev/null @@ -1,54 +0,0 @@ -# Authorization - -Python (in comparision to Javascript) scripts need an authentication. - -You can use two methods to obtain authorization to read and write a base. - -1. One way is to use the api token of the base, the token can be directly generated on the web side. Read directly from context.api_token in the cloud environment. - -1. Another method is to use your username and password to initialize an `account` object, and then call the `account` interface to get a `base` object. The first method is much easier. - -!!! Danger "Protect your credentials" - - Please be aware that a python script is readable for all users, who have access to this base. Therefore be careful if you store your username and password to a python script. - -## Authorization with API-Token - -```python -from seatable_api import Base, context -base = Base(context.api_token, context.server_url) -base.auth() -``` - -## Authorization with account object - -```python -from seatable_api import Account -account = Account(username, password, server_url) -account.auth() -base = account.get_base(workspace_id, base_name) -``` - -## Authorization expiration handling - -!!! note "Note, this feature works with SeaTable version 3.1+" - -In some cases, the program need to run for a long time, we put the base operation code into a while or for loop. Authorization may expire during execution and cause the program to break. We provide an exception called `AuthExpiredError` that can be caught for reauthorization. - -```python -from seatable_api import Base, context -from seatable_api.exception import AuthExpiredError - -server_url = context.server_url or 'https://cloud.seatable.io' -api_token = context.api_token or 'c3c75dca2c369849455a39f4436147639cf02b2d' - -base = Base(api_token, server_url) -base.auth() - -while True: - try: - base.append_row('Table1', {"xxx":"xxx"}) - ... - except AuthExpiredError: - base.auth() -``` diff --git a/docs/scripts/python/basic_structure_python.md b/docs/scripts/python/basic_structure_python.md deleted file mode 100644 index 5b6362e8..00000000 --- a/docs/scripts/python/basic_structure_python.md +++ /dev/null @@ -1,97 +0,0 @@ -# Basic structure of a Python script - -Python scripts can be and executed directly in a base using a SeaTable component called Python Pipeline. You can also choose to run scripts locally. Where you run your Python script has consequences on the available libraries and authentication. - -## Libraries - -The current Python Pipeline ships with Python 3.12 and a bundle of [third party libraries](/scripts/python/common_questions/#list-of-libraries-supported-in-the-cloud-environment). One of the bundled library and the main library to interact with SeaTable bases is [seatable-api](https://github.com/seatable/seatable-api-python). - -At a minimum, the Base and context function from the seatable-api library must be imported. Additionally, you can import functions from the bundled libraries. - -```python -from seatable_api import Base, context -from datetime import datetime -``` - -When running Python scripts locally, you can take advantages of the uncountable number of Python libraries. - -## Authentication - -As a general rule, Python script must authenticate. - -Within SeaTable's integrated Python editor, authentication can be done using these two lines of code at the beginning of the script thanks to the [context object](https://developer.seatable.com/scripts/python/objects/context/): - -```python -base = Base(context.api_token, context.server_url) -base.auth() -``` - -Read here all details about [authentication in Python scripts](/scripts/python/authorization_python/). - -!!! warning "Multiple tokens in SeaTable" - - SeaTable provides multiple tokens to authenticate. But let's keep things simple! If you develop Python scripts in SeaTable, just use the context object `context.api_token` or provide a so called `API-Token` of a base. - - All details can be found in the [SeaTable API Reference](https://api.seatable.com/reference/authentication). - -It is even possible to develop a Python in the way that it could be [executed in the cloud and local](/scripts/python/common_questions/#install-and-use-custom-python-libraries) without changing the code. - -## Objects and methods - -There are a lot of predefined objects and methods in Python. If you compare JavaScript and Python, you will notice that Python has no output object. This is not necessary, because the output is either written directly into the base or printed. - -## Let's get concrete - -Let's make this concrete and let us look at some basic examples. - -1. Jump to your SeaTable web interface -2. Create a new script of the type Python -3. Copy the following code -4. Run the script - -You will learn from these examples, that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. - -!!! danger "Indents are important" - - Please take care of indentations! Indentation is mandatory in Python to define the blocks of statements. The number of spaces must be uniform in a block of code. It is preferred to use whitespaces instead of tabs to indent in Python. If the indentations are wrong, the scripts will throw errors or not work as expected! - -=== "Add a table to a base" - - This examples shows how to add a table to an existing bases. - - ``` python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() # (1)! - - columns=[ - { - "column_type" : "text", - "column_name": "name" - }, - { - "column_type": "number", - "column_name": "age" - } - ] - - base.add_table("ScriptTest", lang='en', columns=columns) - ``` - - 1. These three lines are always required to authorize against the base in SeaTable. - -=== "Add a row to a table" - This examples shows how to add a a record to a table. The example script assumes that a table "ScriptTest" table with two columns "name" and "age" exists in the base. - - ``` python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() - - row_data = { - 'name': 'Tom', - 'age': 18 - } - - base.append_row('ScriptTest', row_data) - ``` diff --git a/docs/scripts/python/common_questions.md b/docs/scripts/python/common_questions.md index 7669a147..faeedb11 100644 --- a/docs/scripts/python/common_questions.md +++ b/docs/scripts/python/common_questions.md @@ -86,4 +86,52 @@ ## Install and use custom python libraries - The python libraries in SeaTable Cloud can not be changed. - - If you run your own SeaTable Server it is possible to install your own libaries. + - If you run your own SeaTable Server it is possible to install your own libraries. + +??? question "Printing complex elements (dicts, tables, arrays of rows) is sometimes difficult to read" + + ## Printing complex elements is sometimes difficult to read + + Do not hesitate to run your code in a Python IDE which could have specific features for data visualization (don't forget you won't be able to rely on context to provide `api_token` and `server_url`, see first question for dual run syntax). You could also use the `json` library to make the output of complex objects easier to read: + + ```python + import json # (1)! + from seatable_api import Base,context + base = Base(context.api_token,context.server_url) + base.auth() + + print(json.dumps(base.get_metadata(), indent=' ')) # (2)! + ``` + + 1. Import the json library + + 2. Print `json.dumps(object, indent='  ')` instead of just printing object. You have to explicitly specify the indent character (which is not a classic space character) as the output window of SeaTable's script editor actually trims indent spaces. + +??? question "How to deal with more than 1000 rows at once with batch operations?" + + ## Dealing with more than 1000 rows at once with batch operations + + As presented in the [API Reference](https://api.seatable.com/reference/limits), batch operations such as `base.batch_append_rows`, `base.batch_update_rows`, `base.batch_delete_rows` or `base.batch_update_links` have a maximum number of 1000 rows. To deal with a higher number of rows, you could: + + - Use an `INSERT`, `UPDATE` or `DELETE` [SQL query](/scripts/sql/introduction.md#supported-sql-syntax) that can operate on an unlimited number of rows + + - Use a `while` loop to split you operation into 1000-rows chunks for example (however this won't exactly be a single operation anymore): + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + + # You want to batch append new_rows which is more than 1000-rows long + while len(new_rows)>0 : + end = min(1000,len(new_rows)) + rows_chunk = new_rows[:end] + print(f"{rows_chunk[0]['Name']} > {rows_chunk[-1]['Name']}") + base.batch_append_rows("Table1", rows_chunk) + new_rows = new_rows[end:len(new_rows)] + ``` + + To [batch update links](./objects/links.md#update-links), the loop will be slightly more complex as you'll have to deal with `other_rows_ids_map` as well + + diff --git a/docs/scripts/python/examples/auto-add-rows.md b/docs/scripts/python/examples/auto-add-rows.md new file mode 100644 index 00000000..b71b1250 --- /dev/null +++ b/docs/scripts/python/examples/auto-add-rows.md @@ -0,0 +1,62 @@ +{% + include-markdown "includes.md" + start="" + end="" +%} + +Unlike JavaScript, Python scripts allow you to handle single- or multiple-select options, which make you capable of checking if the needed options exist and of creating them if necessary directly inside the script. + +```python +from seatable_api import Base, context +from seatable_api.date_utils import dateutils +""" +This script add two expenses rows in a ledger. Before adding them, +it checks if they have already been added for the current month. +""" + +base = Base(context.api_token, context.server_url) +base.auth() + +# Get date objects on the 10th and 20th of the current month +date = dateutils.today() +date10 = dateutils.date(dateutils.year(date), dateutils.month(date), 10) +date20 = dateutils.date(dateutils.year(date), dateutils.month(date), 20) + +# Check if the options you will need already exist, and create them if necessary +options_to_add = [] +current_options = base.get_column_by_name('Daily expenses', 'Type (single select)')['data']['options'] +cloud_service_option = [o for o in current_options if o['name'] == 'Cloud service'] +if not cloud_service_option : + options_to_add.append({"name": "Cloud service", "color": "#aaa", "textColor": "#000000"}) +daily_office_option = [o for o in current_options if o['name'] == 'Daily office'] +if not daily_office_option : + options_to_add.append({"name": "daily office", "color": "#aaa", "textColor": "#000000"}) +if options_to_add : + base.add_column_options('Daily expenses', 'Type (single select)', options_to_add) + +# Check if the monthly expense items have already been created and eventually create them +feeAWS = {} +feeAWSCurrentMonth = base.query('select * from `Daily expenses` where Name="Amazon Cloud Service" and Date="' + date10 + '"') +if not feeAWSCurrentMonth : + feeAWS = {'Name': 'Amazon Cloud Service', + 'Date': date10, + 'Type': 'Cloud service', + 'Type (single select)': 'Cloud service', + } + +feeClean = {} +feeCleanCurrentMonth = base.query('select * from `Daily expenses` where Name="Clean" and Date ="' + date20 + '"') +if not feeCleanCurrentMonth : + feeClean = {'Name': 'Clean', + 'Date': date20, + 'Type': 'Daily office', + 'Type (single select)': 'Daily office', + 'Fee': 260 + } + +# Create the monthly expense items (if needed) +if (feeAWS) : + base.append_row('Daily expenses', feeAWS); +if (feeClean) : + base.append_row('Daily expenses', feeClean); +``` \ No newline at end of file diff --git a/docs/scripts/python/examples/calculate-accumulated-value.md b/docs/scripts/python/examples/calculate-accumulated-value.md new file mode 100644 index 00000000..96118b0c --- /dev/null +++ b/docs/scripts/python/examples/calculate-accumulated-value.md @@ -0,0 +1,65 @@ +{% + include-markdown "includes.md" + start="" + end="" +%} + +```python +from seatable_api import Base, context +from seatable_api.date_utils import dateutils +""" +This script accumulates the values of the current row and the previous rows, +and records the result to the current row (as the *Calculate accumulated value* +operation from the data processing menu). +""" + +base = Base(context.api_token, context.server_url) +base.auth() + +table_name = 'Accumulated value' +view_name = 'Default View' + +# Name of the column that records total number at a specific time +value_column_name = 'Value to add' +# Name of the column that need to calculate incremental value +incremental_column_name = 'Incremental total' + +view = base.get_view_by_name(table_name, view_name) +rows = base.list_rows(table_name, view_name) + +# If current view is a grouped view +if 'groupbys' in view and len(view['groupbys']) > 0 : +# # Get group view rows + grouping_column = [c for c in base.list_columns(table_name) if 'column_key' in view['groupbys'][0] and c['key'] == view['groupbys'][0]['column_key']] + if grouping_column and len(grouping_column) == 1 : + grouping_column_name = grouping_column[0]['name'] + group_values = [] + for row in rows : + if row[grouping_column_name] not in group_values : + group_values.append(row[grouping_column_name]) + for value in group_values : + group_rows = [r for r in rows if r[grouping_column_name] == value] + incremental_total = 0 + for row_index,row in enumerate(group_rows) : + current_number = row[value_column_name]; + if current_number : + # Calculate increment + # If there is no previous row, set increase_count to 0 + previous_number = 0 if row_index == 0 else incremental_total + increase_count = current_number + previous_number + incremental_total = increase_count + # Set calculated increment to row + base.update_row(table_name, row['_id'], {incremental_column_name: increase_count}) +else : + incremental_total = 0 + for row_index,row in enumerate(rows) : + current_number = row[value_column_name]; + if current_number : + # Calculate increment + # If there is no previous row, set increase_count to 0 + previous_number = 0 if row_index == 0 else incremental_total + increase_count = current_number + previous_number + incremental_total = increase_count + # Set calculated increment to row + base.update_row(table_name, row['_id'], {incremental_column_name: increase_count}) +``` \ No newline at end of file diff --git a/docs/scripts/python/examples/compute-attendance-statistics.md b/docs/scripts/python/examples/compute-attendance-statistics.md new file mode 100644 index 00000000..7f4dc8f9 --- /dev/null +++ b/docs/scripts/python/examples/compute-attendance-statistics.md @@ -0,0 +1,83 @@ +{% + include-markdown "includes.md" + start="" + end="" +%} + +```python +from seatable_api import Base, context +""" +This script computes, from a list of clocking times, +daily clock in (earliest clocking) and clock out +(latest clocking) times for each day and staff member. +""" + +base = Base(context.api_token, context.server_url) +base.auth() + +origin_table_name = 'Clocking table' +origin_view_name = 'Default View' +origin_name_column_name = 'Name' +origin_department_column_name = 'Department' +origin_date_column_name = 'Date' +origin_time_column_name = 'Clocking time' + +target_table_name = 'Attendance statistics' +target_name_column_name = 'Name' +target_department_column_name = 'Department' +target_date_column_name = 'Date' +target_start_time_column_name = 'Clock-in' +target_end_time_column_name = 'Clock-out' + +def get_date(e): + return e[origin_date_column_name] + +#table = base.getTableByName(origin_table_name) +#view = base.getViewByName(table, origin_view_name) +rows = base.list_rows(origin_table_name, origin_view_name) + +# Sort the rows in the Clocking table according to the date column +rows.sort(key=get_date) + +# Group all rows via date and save them to groupedRows, the format +# of the object is {'2020-09-01': [row, ...], '2020-09-02': [row, ...]} +grouped_rows = {} +date_stat_items = [] +for row in rows : + date = row[origin_date_column_name] + if date not in grouped_rows : + grouped_rows[date] = [] + grouped_rows[date].append(row) + +# Traverse all the groups in grouped_rows +for date_key in grouped_rows : + # Get all clocking data of all members for the current date + date_rows = grouped_rows[date_key] + staff_date_stat_item = {} + # Traverse these rows and group by the name of the employee, get the clock-in and clock-out time of each employee that day, and save it to staffDateStatItem + # the format is { EmployeeName: {Name: 'EmployeeName', Date: '2020-09-01', Clock-in: '08:00', Clock-out: '18:00'},... } + for row in date_rows : + name = row[origin_name_column_name] + if name not in staff_date_stat_item : + # Generate a new row based on the original row data, and add Clock-in and Clock-out columns in the newly generated row + staff_date_stat_item[name] = { + target_name_column_name: name, + target_date_column_name: row[origin_date_column_name], + target_department_column_name: row[origin_department_column_name], + target_end_time_column_name: row[origin_time_column_name], + target_start_time_column_name: row[origin_time_column_name] + } + else : + # When another record (same employee and same date) is found, compare the time, choose the latest one as the Clock-out time, and the earliest one as the Clock-in time + time = row[origin_time_column_name] + staff_item = staff_date_stat_item[name] + if staff_item[target_start_time_column_name] > time : + staff_item[target_start_time_column_name] = time + elif staff_item[target_end_time_column_name] < time : + staff_item[target_end_time_column_name] = time + for staff in staff_date_stat_item : + date_stat_items.append(staff_date_stat_item[staff]) + +# Write the attendance data of all employees on the current date into the table +base.batch_append_rows(target_table_name,date_stat_items) +``` \ No newline at end of file diff --git a/docs/scripts/python/examples/generate_barcode.md b/docs/scripts/python/examples/generate_barcode.md index 6f0c72b5..92e5fb79 100644 --- a/docs/scripts/python/examples/generate_barcode.md +++ b/docs/scripts/python/examples/generate_barcode.md @@ -1,24 +1,22 @@ # Generate Barcode -This Python script demonstrates the process of converting text slices into barcode images and storing them in an image column within SeaTable. +This Python script demonstrates the process of converting text slices into barcode images using the `barcode` module and storing them in an image column within SeaTable. It offers an automated way to generate barcode images from text data in a SeaTable table, enhancing data visualization and association within the SeaTable platform. -## Functionality +Here is the structure of the table named `Generate 1 or 2D barcodes` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): -- Setup and Authentication: Utilizes SeaTable API credentials for authentication. -- Barcode Generation: Processes rows in a specified SeaTable table, extracting text from a designated column (TEXT_COL) to generate barcode images. -- Association with Records: Associates generated barcode images with the respective records by updating an image column (BARCODE_IMAGE_COL) in the SeaTable table. -- Handling Existing Images: Skips rows if a barcode image already exists for efficient processing. -- Customization Options: Provides customizable parameters such as module width, height, padding, font size, and text-barcode distance for barcode image generation. -- Error Handling: Includes exception handling to manage errors encountered during the barcode image generation process. +| Column name | Message | Barcode image | +| ----------- |: ------ :|: ------ :| +| **Column type** | text | image | + +This table can be shared with the [Generate QR code example](./generate_qrcode.md) by adding it an extra *QRcode image* image-type column. ## Process Overview -1. Iterates through rows in a specified SeaTable table. -1. Converts text data from a designated column into barcode images using the specified barcode type (BARCODE_TYPE). -1. Saves the generated barcode images temporarily. -1. Uploads the generated barcode images to SeaTable and associates them with corresponding records. -1. Removes temporary barcode image files after successful upload. -1. This script offers an automated way to generate barcode images from text data in a SeaTable table, enhancing data visualization and association within the SeaTable platform. +1. **Iterates through rows** in a SeaTable table whose name is specified in the `TABLE_NAME` variable and check if a barcode already exists for each row (operates only on rows without barcode). Includes exception handling to manage errors encountered during the barcode image generation process. +2. **Converts text data** from a designated column (`TEXT_COL`) into barcode images using the specified barcode type (`BARCODE_TYPE`). +3. **Saves the generated barcode images** temporarily. +4. **Uploads the generated barcode images** to SeaTable and associates them with corresponding records (in the `BARCODE_IMAGE_COL` column). +5. **Removes temporary barcode image files** after successful upload. ## Code @@ -33,17 +31,17 @@ The python script shows how to transfer a slice of text into a barcode image and the image column """ -api_token = context.api_token or "859ad340d9a2b11b067c11f43078992e14853af5" +api_token = context.api_token or "859ad340d9a2b...8992e14853af5" server_url = context.server_url or "https://cloud.seatable.io" +TABLE_NAME = 'Generate 1 or 2D barcodes' TEXT_COL = "Message" # column which is expected to be transferred into barcode -BARCODE_IMAGE_COL = "BarcodeImage" -TABLE_NAME = 'Table1' +BARCODE_IMAGE_COL = "Barcode image" BARCODE_TYPE = 'code128' CUSTOM_OPTIONS = { "module_width": 0.2, # width of single stripe of barcode, mm - "module_height": 15.0, # height of barcode, mm + "module_height": 30.0, # height of barcode, mm "quiet_zone": 6.5, # padding size of first and last stripe to the image, mm "font_size": 10, # font size of the text below the barcode,pt "text_distance": 5.0, # distance between the text and the barcode, mm @@ -58,28 +56,29 @@ def get_time_stamp(): return str(int(time.time()*100000)) updated_rows = 0 +# 1. Iterate through rows for row in base.list_rows(TABLE_NAME): - # continue if the image is already shown up here + # 1.b Continue if the image is already shown up here if row.get(BARCODE_IMAGE_COL): continue - + # 1.c Error handling try: row_id = row.get('_id') msg = str(row.get(TEXT_COL)) - # create a barcode object + # 2. Create a barcode object code_img = CODE(msg, writer=ImageWriter()) - save_name = "%s_%s" % (row_id, get_time_stamp()) - # temporarily saved as an image + # 3. Temporarily save the image + save_name = "%s_%s" % (row_id, get_time_stamp()) file_name = code_img.save("/tmp/%s" % save_name, options=CUSTOM_OPTIONS) - # upload the barcode image to the base + # 4. Upload the barcode image to the base and associate it to the row info_dict = base.upload_local_file(file_name, name=None, file_type='image', replace=True) img_url = info_dict.get('url') base.update_row(TABLE_NAME, row_id, {BARCODE_IMAGE_COL: [img_url]}) - # remove the image file which is saved temporarily + # 5. Remove the image file which was saved temporarily os.remove(file_name) updated_rows += 1 except Exception as error: diff --git a/docs/scripts/python/examples/generate_qrcode.md b/docs/scripts/python/examples/generate_qrcode.md index a59c2f68..a44c098e 100644 --- a/docs/scripts/python/examples/generate_qrcode.md +++ b/docs/scripts/python/examples/generate_qrcode.md @@ -1,24 +1,22 @@ -# Generate QR-Code +# Generate QR code -This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. It uses the `seatable_api` library and `qrcode` module to accomplish this task. +This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. In addition to `seatable_api` library, it uses the `qrcode` module to accomplish this task. In comparison to the [Generate barcode example], this example adds an `OVERWRITE` parameter to choose if existing QRcodes should be recreated or not. -## Functionality +Here is the structure of the table named `Generate 1 or 2D barcodes` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): -- Setup and Authentication: Utilizes SeaTable API credentials for authentication. -- QR Code Generation: Processes rows in a specified SeaTable table, extracting text from a designated column (STRING_COLUMN) to generate QR codes. -- Association with Records: Associates generated QR code images with the respective records by updating an image column (IMAGE_COLUMN) in the SeaTable table. -- Handling Existing Images: Allows the option to skip rows if an image already exists or overwrite existing barcode images based on the OVERWRITE flag. -- Error Handling: Provides error handling to manage exceptions during the QR code generation process. +| Column name | Message | QRcode image | +| ----------- |: ------ :|: ------ :| +| **Column type** | text | image | -## Process Overview +This table can be shared with the [Generate barcode example](./generate_barcode.md) by adding it an extra *Barcode image* image-type column. -1. Iterates through rows in a specified SeaTable table. -1. Generates QR codes based on the text content in the designated column. -1. Saves the QR code images temporarily. -1. Uploads the generated images to SeaTable and associates them with corresponding records. -1. Removes temporary image files after successful upload. +## Process Overview -This script offers an automated way to generate QR codes from text data in a SeaTable table, enhancing data visualization and association within the SeaTable platform. +1. **Iterates through rows** in a SeaTable table whose name is specified in the `TABLE_NAME` variable and check if a QRcode already exists for each row (operates either on all rows or only on rows without QRcodes depending on the `OVERWRITE` parameter). Includes exception handling to manage errors encountered during the barcode image generation process. +2. **Generates QR codes** based on the text content in the designated column (`TEXT_COL`). +3. **Saves the QR code images** temporarily. +4. **Uploads the generated images** to SeaTable and associates them with corresponding records (in the `QRCODE_IMAGE_COL` column). +5. **Removes temporary image files** after successful upload. ## Code @@ -27,13 +25,18 @@ import os import time import qrcode from seatable_api import Base, context +""" +The python script shows how to transfer a slice of text into a QR code image and save it into +the image column +""" -api_token = context.api_token or "..." +api_token = context.api_token or "859ad340d9a2b...8992e14853af5" server_url = context.server_url or "https://cloud.seatable.io" -STRING_COLUMN = "String" # text column which is expected to be transferred into qrcode -IMAGE_COLUMN = "Image" -TABLE_NAME = "Table1" +TABLE_NAME = "Generate 1 or 2D barcodes" +TEXT_COL = "Message" # text column which is expected to be transferred into QR code +QRCODE_IMAGE_COL = "QR code image column" + OVERWRITE = True # set to True to overwrite existing barcode images base = Base(api_token, server_url) @@ -50,35 +53,40 @@ def get_time_stamp(): return str(int(time.time() * 100000)) def main(): + # 1. Iterate through rows for row in base.list_rows(TABLE_NAME): - if not OVERWRITE and row.get(IMAGE_COLUMN): + # 1.b Continue if the image is already shown up here + # and OVERWRITE parameter is not True + if not OVERWRITE and row.get(QRCODE_IMAGE_COL): print("Skipping row. Image already exists.") continue - + # 1.c Error handling try: row_id = row.get('_id') - message = row.get(STRING_COLUMN) + message = row.get(TEXT_COL) + # Check if message isn't empty before processing if not message: print("Skipping row. Empty message.") continue - # clear, add data and make an qrcode object + # 2. Clear, add data and make a QRCode object qr.clear() qr.add_data(str(message)) qr.make() img = qr.make_image(fill_color="black", back_color="white") + # 3. Temporarily save the image save_name = f"{row_id}_{get_time_stamp()}" img.save(f"/tmp/{save_name}.png") - # temporarily saved as an image + # 4. Upload the QR code image to the base and associate it to the row info_dict = base.upload_local_file(f"/tmp/{save_name}.png", name=None, file_type='image', replace=True) img_url = info_dict.get('url') - base.update_row(TABLE_NAME, row_id, {IMAGE_COLUMN: [img_url]}) + base.update_row(TABLE_NAME, row_id, {QRCODE_IMAGE_COL: [img_url]}) - # remove the image file which is saved temporarily + # 4. Remove the image file which was saved temporarily os.remove(f"/tmp/{save_name}.png") except Exception as exception: print("Error occurred during Image generation:", exception) diff --git a/docs/scripts/python/examples/heic_to_png.md b/docs/scripts/python/examples/heic_to_png.md index 74becec5..cd8c7f76 100644 --- a/docs/scripts/python/examples/heic_to_png.md +++ b/docs/scripts/python/examples/heic_to_png.md @@ -4,16 +4,22 @@ The library `pillow_heif` was added with the Python Runner version 4.1.1. If you're using SeaTable Cloud, this was added with v5.1. -This Python script demonstrates how to convert HEIC image files to PNG format and save the converted file into a new row in a SeaTable base. It utilizes the `pillow_heif` library to handle HEIC files, `Pillow` for image processing, and the `seatable_api` library to interact with SeaTable. +This Python script demonstrates how to convert HEIC image files to PNG format and save the converted file into a new row in a SeaTable base. It uses the `pillow_heif` library to handle HEIC files, `Pillow` for image processing, and the `seatable_api` library to interact with SeaTable. The script processes **one HEIC file per row**; if you need to handle multiple HEIC files per row, you'll need to modify the script accordingly. + +Here is the structure of the table named `Convert images` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): + +| Column name | HEIC | PNG | +| ----------- |: ------ :|: ------ :| +| **Column type** | image | image | ## Script Overview The script performs the following steps: -- **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. -- **Download HEIC Files:** Retrieves HEIC files from a specified column in SeaTable. -- **Convert HEIC to PNG:** Transforms the downloaded HEIC file to PNG format. -- **Upload Converted PNG:** Uploads the PNG file back to SeaTable and updates the row with the new file URL. +1. **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. +2. **Download HEIC Files:** Retrieves HEIC files from the `HEIC` column in SeaTable. +3. **Convert HEIC to PNG:** Transforms the downloaded HEIC file to PNG format with 90% quality using `Pillow`. +4. **Upload Converted PNG:** (a) Uploads the PNG file back to SeaTable and (b) updates the row with the new file URL in the `PNG` column. ## Example Script @@ -22,16 +28,19 @@ import requests from PIL import Image from pillow_heif import register_heif_opener from seatable_api import Base, context +""" +This Python script demonstrates how to convert HEIC image files + to PNG format and save the converted file into a new row in a SeaTable base. +""" # Activate heif/heic support -register_heif_opener() +register_heif_opener() # (1)! -# >>> UPDATE THESE VALUES ACCORDING TO YOUR NEEDS -TABLE_NAME = "Table1" +TABLE_NAME = "Convert images" FILE_COLUMN = "HEIC" RESULT_COLUMN = "PNG" -# Authentication +# 1. Authentication base = Base(context.api_token, context.server_url) base.auth() @@ -39,39 +48,26 @@ for row in base.list_rows(TABLE_NAME): if row.get(FILE_COLUMN) is None: continue - # input must be image.heic, output is image-xxx.png - filename_heic = 'image.heic' - filename_png = f'image-{row["_id"]}.png' - - # Download heic image + # 2. Download heic image url = row.get(FILE_COLUMN)[0] + filename_heic = url.split('/')[-1] base.download_file(url, filename_heic) - # transform image to png + # 3. Transform image to png im = Image.open(filename_heic) + filename_png = f'image-{row["_id"]}.png' im.save(filename_png, quality=90) print('Saved image') - # Upload + # 4.a) Upload info_dict = base.upload_local_file(filename_png, name=None, file_type='image', replace=True) print('Uploaded file') - # Save back to SeaTable Base + # 4.b) Save back to SeaTable Base img_url = info_dict.get('url') base.update_row(TABLE_NAME, row['_id'], {RESULT_COLUMN: [img_url]}) print('Stored image info in base') ``` -## Detailed Steps - -- **HEIC Support Activation:** The script uses register_heif_opener() to enable HEIC file support. -- **Authentication:** The script uses Base from seatable_api to authenticate using the API token and server URL. -- **File Handling:** It downloads HEIC files specified in the HEIC column for each row. The files are saved locally as image.heic. -- **Conversion Process:** The script uses Pillow to open the HEIC file and save it as a PNG with 90% quality. -- **Uploading and Updating:** The converted PNG file is uploaded back to SeaTable, and its URL is stored in the PNG column of the same row. - -## Usage Notes +1. Note the `register_heif_opener()` call to enable HEIC file support. -Ensure that each row in `Table1` contains a HEIC-file in the `HEIC` column for successful execution. -Adjust table and column names as necessary to fit your specific SeaTable configuration. -The script processes one HEIC file per row. If you need to handle multiple HEIC files per row, you'll need to modify the script accordingly. diff --git a/docs/scripts/python/examples/index.md b/docs/scripts/python/examples/index.md index d5a6270d..94830f52 100644 --- a/docs/scripts/python/examples/index.md +++ b/docs/scripts/python/examples/index.md @@ -1,32 +1,54 @@ # Examples -This section contains some examples of Python Scripts. You can just copy&paste them in any base in SeaTable any run them. +This section contains some examples of Python Scripts. The **first three scripts** are the same than in the JavaScript section. -## SeaTable Email Sender +Even if Python scripts are capable of checking if the base structure (tables and columns) needed exist and of creating it if necessary, we didn't implement this feature in the scripts so you can focus on the actual goal of each script. -This Python script demonstrates sending emails via SMTP using the smtplib library and constructing MIME objects to compose rich content emails within SeaTable. +For each example, you'll then need a special base structure so that you can just copy&paste the scripts into SeaTable and run them. + +# Add rows + +This script demonstrates how to add rows to record monthly repetitive expenses in a ledger. + +[read more :material-arrow-right-thin:](/scripts/javascript/examples/auto-add-rows/) + +## Calculate accumulated value + +This script computes an accumulated value (adds the value of the current row and the previous rows), similar to the *Calculate accumulated value* operation from the data processing menu. + +[read more :material-arrow-right-thin:](/scripts/javascript/examples/calculate-accumulated-value/) + +## Statistics + +This script computes, from a list of clocking times, daily clock in (earliest clocking) and clock out (latest clocking) times for each day and staff member. + +[read more :material-arrow-right-thin:](/scripts/javascript/examples/compute-attendance-statistics/) + +## Email sender + +This Python script demonstrates sending emails via SMTP using the smtplib module and constructing MIME objects to compose rich content emails within SeaTable. [read more :material-arrow-right-thin:](/scripts/python/examples/send_email/) -## SeaTable Barcode Image Generator +## Barcode generator This Python script demonstrates the process of converting text slices into barcode images and storing them in an image column within SeaTable. [read more :material-arrow-right-thin:](/scripts/python/examples/generate_barcode/) -## SeaTable QR Code Generator +## QR code generator -This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. It uses the seatable_api library and qrcode module to accomplish this task. +This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. It uses the seatable_api library and qrcode library to accomplish this task. [read more :material-arrow-right-thin:](/scripts/python/examples/generate_qrcode/) -## SeaTable MySQL Synchronization +## MySQL synchronization This Python script facilitates the synchronization of data from a MySQL database to a SeaTable table. [read more :material-arrow-right-thin:](/scripts/python/examples/sync_mysql/) -## Watch Stock Price +## Watch stock price Integrating data from the Twelve Data API with SeaTable facilitates the updating and maintenance of current stock prices within a designated table in the SeaTable environment. @@ -34,7 +56,7 @@ Integrating data from the Twelve Data API with SeaTable facilitates the updating ## Merge PDF -Merge two PDF files and save the merged file into a new row in a SeaTable base. +Merge PDF files and save the merged file into a new row in a SeaTable base. [read more: :material-arrow-right-thin:](/scripts/python/examples/merge_pdf/) diff --git a/docs/scripts/python/examples/merge_pdf.md b/docs/scripts/python/examples/merge_pdf.md index 60a07bc0..cfe82c0e 100644 --- a/docs/scripts/python/examples/merge_pdf.md +++ b/docs/scripts/python/examples/merge_pdf.md @@ -4,35 +4,43 @@ The library `pdfmerge` was added with the Python Runner version 4.1.1. If you're using SeaTable Cloud, this was added with v5.1. -This Python script demonstrates how to merge two PDF files and save the merged file into a new row in a SeaTable base. It utilizes the `pdfmerge` library to handle the PDF merging process and the `seatable_api` library to interact with SeaTable. +This Python script demonstrates how to merge several PDF files and save the merged file into a new column in a SeaTable base. It utilizes the `pdfmerge` library to handle the PDF merging process and the `seatable_api` library to interact with SeaTable. + +Here is the structure of the table named `Merge PDF` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): + +| Column name | PDF files | Merged file | +| ----------- |: ------ :|: ------ :| +| **Column type** | file | file | ## Script Overview The script performs the following steps: -- **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. -- **Download PDF Files:** Retrieves two PDF files from a specified column in SeaTable. -- **Merge PDFs:** Combines the downloaded PDF files into a single PDF using pdfmerge. -- **Upload Merged PDF:** Uploads the merged PDF back to SeaTable and updates the row with the new file. +1. **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. +2. **Retrieve the files:** For each row, the script gets the name and URL of every file in the `PDF files` column. +3. **Download PDF Files** +4. **Merge PDFs:** Combines the downloaded PDF files using `pdfmerge` into a single PDF named with the pattern `output-{row_id}.pdf`. +5. **Upload Merged PDF:** Uploads the merged PDF back to SeaTable and updates the row with the new file in the `Merged file` column. ## Example Script ```python import os -import pdfmerge import requests import sys import shutil from pdfmerge import pdfmerge from seatable_api import Base, context +""" +This Python script demonstrates how to merge PDF +files and save the merged file into a new column. +""" -# >>> UPDATE THESE VALUES ACCORDING TO YOUR NEEDS -TABLE_NAME = "Table1" -FILE_COLUMN = "PDF Files" -RESULT_COLUMN = "Merged Files" -FILENAMES = ['file-1.pdf', 'file-2.pdf'] +TABLE_NAME = "Merge PDF" +FILE_COLUMN = "PDF files" +RESULT_COLUMN = "Merged file" -# Authentication +# 1. Authentication base = Base(context.api_token, context.server_url) base.auth() @@ -41,33 +49,25 @@ for row in base.list_rows(TABLE_NAME): if row.get(FILE_COLUMN) is None: continue - files = [file['url'] for file in row[FILE_COLUMN]] - assert len(files) == 2 + # 2. Retrieve all files from the row + files = [{'name': file['name'], 'URL': file['url']} for file in row[FILE_COLUMN]] + file_names = [] - # Download pdfs - base.download_file(files[0], FILENAMES[0]) - base.download_file(files[1], FILENAMES[1]) - print('Downloaded 2 files') + # 3. Download PDFs + for f in files : + base.download_file(f['URL'],f['name']) + file_names.append(f['name']) + assert len(file_names) == len(files) + print(f"Downloaded {len(files)} files") - # Merge + # 4. Merge output_filename = f'output-{row["_id"]}.pdf' - pdfmerge(FILENAMES, output_filename) + pdfmerge(file_names, output_filename) print('Merged PDF files') - # Upload file + store URL in base + # 5. Upload file + store URL in the base info_dict = base.upload_local_file(output_filename, name=None, file_type='file', replace=True) + print(info_dict) base.update_row(TABLE_NAME, row['_id'], {RESULT_COLUMN: [info_dict]}) print('Uploaded PDF file') ``` - -## Detailed Steps - -- **Authentication:** The script uses Base from seatable_api to authenticate using the API token and server URL. -- **File Handling:** It downloads two PDFs specified in the `PDF Files` column for each row. The files are saved locally as `file-1.pdf` and `file-2.pdf`. -- **Merging Process:** The pdfmerge function is used to merge these two PDFs into a single file named with the pattern output-{row_id}.pdf. -- **Uploading and Updating:** The merged file is uploaded back to SeaTable, and its URL is stored in the `Merged Files` column of the same row. - -## Usage Notes - -Ensure that each row in `Table1` contains exactly two PDFs in the `PDF Files` column for successful execution. -Adjust table and column names as necessary to fit your specific SeaTable configuration. diff --git a/docs/scripts/python/examples/send_email.md b/docs/scripts/python/examples/send_email.md index b61f84f8..6aef30fb 100644 --- a/docs/scripts/python/examples/send_email.md +++ b/docs/scripts/python/examples/send_email.md @@ -1,109 +1,186 @@ -# Send E-mails +# Send emails -This Python script demonstrates sending emails via SMTP using the smtplib library and constructing MIME objects to compose rich content emails within SeaTable. +This Python script demonstrates sending emails via SMTP using the [smtplib module](https://docs.python.org/3/library/smtplib.html) and constructing MIME objects to compose rich content emails within SeaTable. It also retrieves configuration parameters from the database. This example uses two tables: -## Functionality +- The `Contacts` table storing the contacts you want to send email to: -- Setup and Authentication: Uses SMTP parameters and SeaTable API credentials for authentication and sending emails. -- Recipient Selection: Retrieves email addresses from a SeaTable table's column (Contacts -> Email) to serve as multiple recipients. -- Email Composition: +| Column name | Name | Email | +| ----------- |: ------ :|: ------ :| +| **Column type** | text | email | - - Constructs an email with a specified subject, sender, and HTML content body for the email. - - Allows for attaching files stored in SeaTable records to the email. +- The `Send email config` table storing the email sending parameters: -## Process Overview +| Column name | Subject | Recipient email | Subject source| Email format | Attach file | File | +| ----------- |: ----- :|: ------------- :|: ----------- :|: ---------- :|: --------- :|: -- :| +| **Column type** | text | single select | single select | single select | checkbox | file | -1. Retrieves recipient email addresses from a designated SeaTable table column (Contacts -> Email). -1. Composes an email using HTML content to create a rich-text message body. -1. Attaches a file from SeaTable to the email by fetching its download link using the SeaTable API and attaching it to the email. +- Recipient email can be `hard-coded` (recipients are defined l.39 of the script as a list of email addresses) or `database` (recipients are retrieved from the `Email` column of the `Contacts` table). +- `Subject source` can be `hard-coded` (define manually the subject of the mail l.48 of the script) or `database` (the subject is retrieved from the `Subject` column of the `Send email config` table). +- Email format can be `text` (plain text defined l.61) or `html` (HTML-formatted message, defined l.67). Of course, you can imagine a third `database` option allowing you to retrieve the email body from your `Send email config` table. +- If `Attach file` is checked, the first file from the `File` column will be enclosed (don't forget to adapt the `_subtype` l.98 of the script if your file is not a pdf). +- You can eventually add a `Send email` column, configured to launch the script. The script itself is written to use either the `context.current_row` data, or the first row of the table if no `context` is defined (script launched from outside SeaTable). -This script offers an automated way to send emails with rich content and attachments using data stored within SeaTable, enabling streamlined communication and file sharing within the SeaTable environment. +## Process overview + +1. **Retrieves email configuration** from the `Send email config` table. +2. Eventually **retrieves recipient email addresses** from a designated SeaTable table column (`Email` column in `Contact` table). +3. Eventually **retrieves email subject**. +4. **Composes an email** using plain text or HTML content to create a rich-text message body. +5. **Attaches a file** from SeaTable to the email by fetching its download link using the SeaTable API and attaching it to the email. +6. **Sends the email** after authenticating using SMTP parameters. ## Code -```python -import smtplib +```python linenums="1" +import smtplib, ssl +from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.mime.image import MIMEImage from email.header import Header from urllib import parse import requests from seatable_api import Base, context +""" +This Python script demonstrates sending emails via SMTP +using the smtplib module and constructing MIME objects +to compose rich content emails within SeaTable. +""" # SeaTable API authentication base = Base(context.api_token, context.server_url) base.auth() +CONFIG_TABLE = 'Send email config' +CONTACTS_TABLE = 'Contacts' + # SMTP server configurations for sending emails -smtpserver = 'smtp.163.com' -username = '13069744444@163.com' +smtp_server = 'my.smtpserver.com' +smtp_port = 465 +username = 'my.em@il.com' password = 'topsecret' -sender = '13069744444@163.com' - -# Option a) define the recipient email address in this script -receivers = ['1223408888@qq.com'] - -# Option b) Retrieving recipient email addresses from the 'Contacts' table in SeaTable -receiver_rows = base.list_rows('Contacts') -receivers = [row['Email'] for row in receiver_rows if row.get('Email')] - -# Email subject -subject = 'SeaTable Send email' - -# Constructing the email message -msg = MIMEMultipart('mixed') +sender = 'My name' + +# 1. Get email configuration from the 'Send email config' table +current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] +# Choose RECIPIENT_EMAIL between "hard-coded" (subject l.39 of this script) +# or "database" (get emails from 'Email' column in the 'Contacts' table) +RECIPIENT_EMAIL = current_row.get('Recipient email') +# Choose SUBJECT between "hard-coded" (address l.48 of this script) +# or "database" (get subject from 'Subject' column in the 'Send email config' table) +SUBJECT_SOURCE = current_row.get('Subject source') +# Choose EMAIL_FORMAT between "text" (hard-coded plain text, defined l.61) +# or "html" (hard-coded HTML, defined l.67) +# and "database" (content of the 'Message' column in the 'Send email config' table) +EMAIL_FORMAT = current_row.get('Email format') +# If Attach file, the script retrieves the first file from the 'File' column of the 'Sending email config' +ATTACH_FILE = current_row.get('Attach file') + +# 2. Set recipient email addresses +if RECIPIENT_EMAIL == "hard-coded" : + # Option a) Define the recipient email address in this script + receivers = ['johndoe@email.com'] +elif RECIPIENT_EMAIL == "database" : + # Option b) Retrieve recipient email addresses from the 'Contacts' table in SeaTable + receiver_rows = base.list_rows(CONTACTS_TABLE) + receivers = [row['Email'] for row in receiver_rows if row.get('Email')] + +# 3. Set email subject +if SUBJECT_SOURCE == "hard-coded" : + # Option a) Define the subject in this script + subject = 'SeaTable Send email' +elif SUBJECT_SOURCE == "database" : + # Option b) Retrieve the subject from the 'Send email config' table + current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] + subject = current_row.get('Subject') + +# 4. Construct the email message +msg = MIMEMultipart() msg['Subject'] = subject -msg['From'] = '13069744444@163.com <13069744444@163.com>' -msg['To'] = ";".join(receivers) - -# Option a) plain text message -# text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.google.com" -# text_plain = MIMEText(text,'plain', 'utf-8') -# msg.attach(text_plain) - -# Option b) HTML content for the email body -html = """ - - - -

Hi!
- This is a sample of messages - from SeaTable -

- - -""" -text_html = MIMEText(html,'html', 'utf-8') -msg.attach(text_html) +msg['From'] = sender + '<' + username + '>' +msg['To'] = ", ".join(receivers) + +if EMAIL_FORMAT == "text" : + # Option a) plain text message + text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.seatable.com.com" + text_plain = MIMEText(text,'plain', 'utf-8') + msg.attach(text_plain) + +elif EMAIL_FORMAT == "html" : + # Option b) HTML content for the email body + html = """ + + + +

Hi!
+ This is a sample message from SeaTable +

+ + + """ + text_html = MIMEText(html,'html', 'utf-8') + msg.attach(text_html) + +# 5. Attach a file from SeaTable to the email +if ATTACH_FILE : + # Get the file from the 'send email config' table + current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] + file_name = current_row['File'][0]['name'] + file_url = current_row['File'][0]['url'] + path = file_url[file_url.find('/files/'):] + download_link = base.get_file_download_link(parse.unquote(path)) + + try: + response = requests.get(download_link) + if response.status_code != 200: + print('Failed to download file, status code: ', response.status_code) + exit(1) + except Exception as e: + print(e) + exit(1) + + # Attach the file to the email (adapt _subtype to the type of your file) + attached_file = MIMEApplication(response.content, _subtype = "pdf") + attached_file.add_header('content-disposition', 'attachment', filename = file_name) + msg.attach(attached_file) + +# 6. Send the email + +# option a) Sending the email using SMTP +try: + with smtplib.SMTP() as email_server: + email_server.connect(smtp_server) + email_server.login(username, password) + email_server.send_message(msg) + email_server.quit() +except smtplib.SMTPAuthenticationError: + print("SMTP User authentication error, Email not sent!") +except Exception as e: + print(f"SMTP exception {e}") -# Attaching a file from SeaTable to the email -rows = base.list_rows('Table3') -filename = rows[0]['File'][0]['name'] -file_url = rows[0]['File'][0]['url'] -path = file_url[file_url.find('/files/'):] -download_link = base.get_file_download_link(parse.unquote(path)) +''' +# option b) Sending the email using SMTP / SSL +ssl_context = ssl.create_default_context() +try: + with smtplib.SMTP_SSL(smtp_server, smtp_port, + context=ssl_context) as email_server: + email_server.login(username, password) + email_server.send_message(msg) + email_server.quit() +except smtplib.SMTPAuthenticationError: + print("SMTP User authentication error, Email not sent!") +except Exception as e: + print(f"SMTP exception {e}") +# option c) Sending the email using SMTP with STARTTLS try: - response = requests.get(download_link) - if response.status_code != 200: - print('Failed to download image, status code: ', response.status_code) - exit(1) + with smtplib.SMTP(smtp_server, smtp_port) as email_server: + email_server.starttls() + email_server.login(username, password) + email_server.send_message(msg) + email_server.quit() +except smtplib.SMTPAuthenticationError: + print("SMTP User authentication error, Email not sent!") except Exception as e: - print(e) - exit(1) - -# Attaching the file to the email -text_att = MIMEText(response.content, 'base64', 'utf-8') -text_att["Content-Type"] = 'application/octet-stream' -text_att["Content-Disposition"] = 'attachment;filename*=UTF-8\'\'' + parse.quote(filename) - -msg.attach(text_att) - -# Sending the email using SMTP -smtp = smtplib.SMTP() -smtp.connect(smtpserver) -smtp.login(username, password) -smtp.sendmail(sender, receivers, msg.as_string()) -smtp.quit() + print(f"SMTP exception {e}") +''' ``` diff --git a/docs/scripts/python/examples/sync_mysql.md b/docs/scripts/python/examples/sync_mysql.md index 8c9a33e2..798fff98 100644 --- a/docs/scripts/python/examples/sync_mysql.md +++ b/docs/scripts/python/examples/sync_mysql.md @@ -1,73 +1,73 @@ # SeaTable MySQL Synchronization -This Python script facilitates the synchronization of data from a MySQL database to a SeaTable table. - -## Functionality - -- Configuration Setup: Defines configurations for SeaTable, specifying server URL and API token, as well as MySQL database connection details. -- Data Sync Process: - - - Establishes a connection to the SeaTable base and authenticates using the provided API token and server URL. - - Retrieves existing rows from a designated SeaTable table (Table1) and extracts specific column (Name) data. - - Connects to the MySQL database (seatable) and fetches data from the order table. - - Compares MySQL data with SeaTable data to identify new records. - - Appends new records found in MySQL but not present in SeaTable to ensure synchronization. +This Python script facilitates the synchronization of data from a MySQL database to a SeaTable table, ensuring consistency and updating records seamlessly. Variables are present at the beginning of the script to easily adapt the names of both Seatable and MySQL tables and columns. The `Sync MySQL` table requires a single `Name` text-type column for the script to be able to run. ## Process Overview -1. Initializes connections to both SeaTable and MySQL databases. -1. Fetches existing rows and column data from the designated SeaTable table (Table1). -1. Retrieves data from the MySQL order table. -1. Compares MySQL data with SeaTable data to identify new records by matching the 'name' field. -1. Adds new records from MySQL to SeaTable (Table1) for synchronization. +1. **Initializes connections** to both (a) SeaTable and (b) MySQL databases. +2. **Fetches existing data** from the `Name` column of the `Sync MySQL` SeaTable table. +3. **Retrieves data** from the MySQL `order` table. +4. **Compares MySQL data with SeaTable data** to identify new records by matching the `name` field. +5. **Adds new records** from MySQL to SeaTable (`Sync MySQL`) for synchronization. -This script enables the automated synchronization of data between a MySQL database and a SeaTable table, ensuring consistency and updating records seamlessly. ## Code ```python import pymysql from seatable_api import Base, context +""" +This Python script facilitates the synchronization of data +from a MySQL database to a SeaTable table, ensuring consistency +and updating records seamlessly. +""" -# Base config +# SeaTable base config SERVER_URL = context.server_url or 'http://127.0.0.1:8000' API_TOKEN = context.api_token or '...' -# Table config -TABLE_NAME = 'Table1' -NAME_COLUMN = 'Name' +# SeaTable table config +ST_TABLE_NAME = 'Sync MySQL' +ST_NAME_COLUMN = 'Name' # MySQL config HOST = 'localhost' -USER = '' -PASSWORD = '' -DB = 'seatable' +USER = 'username' +PASSWORD = 'topsecret' +MYSQL_DB = 'seatable' +MYSQL_TABLE = 'order' +MYSQL_NAME_COLUMN = 'name' def sync_mysql(): - """Sync database into the table - """ - # base initiated and authed + # 1. Initialize connection + # 1. a) SeaTable authentication base = Base(API_TOKEN, SERVER_URL) base.auth() - rows = base.list_rows(TABLE_NAME) - row_keys = [row.get(NAME_COLUMN) for row in rows] + # 1. b) MySQL connection + connection = pymysql.connect(host=HOST, user=USER, password=PASSWORD, db=MYSQL_DB) - # mysql data - connection = pymysql.connect(host=HOST, user=USER, password=PASSWORD, db=DB) + # 2. Fetch existing rows from seaTable + rows = base.list_rows(ST_TABLE_NAME) + row_keys = [row.get(ST_NAME_COLUMN) for row in rows] + # 3. Retrieving data from MySQL with connection.cursor(pymysql.cursors.DictCursor) as cursor: - sql = "SELECT * FROM order" + sql = "SELECT * FROM " + MYSQL_TABLE cursor.execute(sql) mysql_data = cursor.fetchall() - # sync + # Synchronization + rows_data = [] for item in mysql_data: - if item.get('name') not in row_keys: + # 4. Look for data from MySQL not present in SeaTable + if item.get(MYSQL_NAME_COLUMN) not in row_keys: row_data = { - 'Name': item.get('name'), + ST_NAME_COLUMN: item.get(MYSQL_NAME_COLUMN), } - base.append_row(TABLE_NAME, row_data) + # 5. Eventually add missing records + if rows_data : + base.batch_append_rows(TABLE_NAME, rows_data) if __name__ == '__main__': diff --git a/docs/scripts/python/examples/update_stock_price.md b/docs/scripts/python/examples/update_stock_price.md index 4bf9888d..c74b2438 100644 --- a/docs/scripts/python/examples/update_stock_price.md +++ b/docs/scripts/python/examples/update_stock_price.md @@ -1,62 +1,54 @@ -# Stock Price Update with Twelve Data API +# Watch stock price by querying an API -This Python script integrates data from the Twelve Data API with SeaTable to update and maintain current stock prices within a designated SeaTable table. +This Python script demonstrates how to retrieve data from an external source by making a `GET` request to an external API. The [Twelve Data](https://twelvedata.com) API is indeed used to update and maintain current stock prices within a designated SeaTable table. -## Functionality +!!! info "Free subscription and fake/mock APIs" + A free subscription is available for Twelve Data if you just want to test the script (up to 800 calls per days are free). + + If you're interested in querying external APIs, you can find free playground APIs for such purpose such as the very specific [cat API](https://thecatapi.com/), [JSONPlaceholder](https://jsonplaceholder.typicode.com/) or more complex mock API such as [MockFast.io](https://mockfast.io/) allowing you to define the structure of the response for more complex and heavy testing. -- **Configuration Setup**: Defines configurations for the Twelve Data API and SeaTable server, specifying API keys and table/column details within SeaTable. -- **Stock Price Retrieval**: +Here is the structure of the table named `Watch stock` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - - Utilizes the Twelve Data API to retrieve the current stock prices based on stock symbols. - - Makes HTTP GET requests to the Twelve Data API to fetch stock prices using the provided API key. +| Column name | Symbol | Current stock price | +| ----------- |: ------ :|: ------ :| +| **Column type** | text | number (dollar) | -- **SeaTable Update**: - - - Retrieves stock symbols from a designated SeaTable table (Stock Watch). - - Updates the SeaTable table with the current stock prices fetched from the Twelve Data API, populating the designated column (Current stock price) in SeaTable. - - Displays the updated stock prices for each symbol in the console. +You can create several lines to watch current stock price, for example by specifying *AAPL* or *AMZN* for the `Symbol` column. ## Process Overview -1. Initializes configurations for the Twelve Data API and SeaTable server. -1. Fetches current stock prices using the Twelve Data API based on stock symbols. -1. Retrieves stock symbols from the SeaTable table (Stock Watch). -1. Updates the SeaTable table with the fetched current stock prices in the designated column (Current stock price). -1. Displays the updated stock prices for each symbol in the console. +1. **Initializes configurations** for the Twelve Data API and SeaTable server. +2. **Fetches current stock prices** using the Twelve Data API based on stock symbols from a SeaTable table (from the `Symbol` column in the `Watch stock` table). +3. **Updates the SeaTable table** with the fetched current stock prices in the designated column (`Current stock price`). +4. **Displays the updated stock prices** for each symbol in the console. This script enables the automated update of current stock prices within a SeaTable table by leveraging data from the Twelve Data API, ensuring that stock information remains up-to-date within the SeaTable environment. -## SeaTable Base used in this example - -| Symbol | Current stock price | -| ------ | ------------------- | -| AAPL | $198.03 | -| AMZN | $147.40 | - ## Code ```python from seatable_api import Base, context import requests +""" +This Python script integrates data from the Twelve Data API with SeaTable +to update and maintain current stock prices. +""" -TWELVE_DATA_API_KEY = "dfb122bbca6a4..." # Replace this with your actual API key from Twelve Data (up to 800 calls per days are free) +# 1. Configuration variables for both SeaTable and Twelve Data +TWELVE_DATA_API_KEY = "dfb122bbca6a4..." # Replace this with your actual API key from Twelve Data SERVER_URL = context.server_url or "https://cloud.seatable.io/" API_TOKEN = context.api_token or "..." -TABLE_WITH_STOCK_SYMBOLS = "Stock Watch" +TABLE_WITH_STOCK_SYMBOLS = "Stock watch" COLUMN_WITH_STOCK_SYMBOLS = "Symbol" COLUMN_WITH_STOCK_PRICE = "Current stock price" -### -# Do not change anything below this line -### - -# Endpoint to fetch current stock price def get_stock_price(SYMBOL): + # Endpoint to fetch current stock price url = f"https://api.twelvedata.com/price?symbol={SYMBOL}&apikey={TWELVE_DATA_API_KEY}" - # Making the GET request to fetch the data + # Make the GET request to fetch the data response = requests.get(url) if response.status_code == 200: @@ -65,11 +57,14 @@ def get_stock_price(SYMBOL): else: return false -# get symbols from SeaTable base and update the current stock price +# Get symbols from SeaTable base and update the current stock prices def update_stock_price(): for row in base.list_rows(TABLE_WITH_STOCK_SYMBOLS): + # 2. Fetches the current stock price from Twelve Data API current_price = get_stock_price(row['Symbol']) + # 3. Update the stock price in the table base.update_row(TABLE_WITH_STOCK_SYMBOLS, row.get('_id'), {COLUMN_WITH_STOCK_PRICE: current_price}) + # 4. Display the fetched value in the console print(f"The current price of {row['Symbol']} is: {current_price}") if __name__ == '__main__': diff --git a/docs/scripts/python/introduction.md b/docs/scripts/python/introduction.md new file mode 100644 index 00000000..78123e11 --- /dev/null +++ b/docs/scripts/python/introduction.md @@ -0,0 +1,121 @@ +# Introduction + +Python scripts connects to SeaTable databases with the python library [seatable-api](https://pypi.org/project/seatable-api/). You can find the source code on [GitHub](https://github.com/seatable/seatable-api-python). Python scripts can be and executed directly in a base using a SeaTable component called Python Pipeline. You can also choose to run scripts locally. Where you run your Python script has consequences on the available libraries and authentication. + +!!! warning "Indents are important" + + Please take care of indentations! Indentation is mandatory in Python to define the blocks of statements. The number of spaces must be uniform in a block of code. It is preferred to use whitespaces instead of tabs to indent in Python. If the indentations are wrong, the scripts will throw errors or not work as expected! + +## Libraries + +The current Python Pipeline ships with Python 3.12 and a bundle of [third party libraries](/scripts/python/common_questions/#list-of-libraries-supported-in-the-cloud-environment). One of the bundled library and the main library to interact with SeaTable bases is [seatable-api](https://github.com/seatable/seatable-api-python). + +At a minimum, the Base and context function from the seatable-api library must be imported. Additionally, you can import functions from the bundled libraries. + +```python +from seatable_api import Base, context +from datetime import datetime +``` + +When running Python scripts locally, you can take advantages of the uncountable number of Python libraries. + +## Authentication + +Python (in comparison to JavaScript) scripts need an authentication. SeaTable provides multiple tokens to obtain authorization to read and write a base. But let's keep things simple! If you develop Python scripts in SeaTable, just use the context object `context.api_token` or provide a so called `API token` of a base (see [Authorization with API token below](#authorization-with-api-token)). If you want to learn more about authentication, all details can be found in the [SeaTable API Reference](https://api.seatable.com/reference/authentication). + +!!! warning "Protect your credentials" + + Please be aware that a python script is readable for all users, who have access to this base. Therefore try to avoid exposing your credentials directly in the code! Use environment variables or `.venv` files instead. + +### Authorization with API token + +Using this method, you will use the API token of the base. Within SeaTable's integrated Python editor, authentication can be done very simply thanks to the [context object](https://developer.seatable.com/scripts/python/objects/context/). In local environment, the context object is not available. You'll have to provide directly the `api_token` and the `server_url` variables. The API token can be directly [generated in the web interface](https://seatable.com/help/erzeugen-eines-api-tokens/). + +=== "SeaTable's integrated Python editor" + + ```python + from seatable_api import Base, context # (1)! + base = Base(context.api_token, context.server_url) + base.auth() + ``` + + 1. Don't forget to import `context`. Thanks to this, you won't have to manually provide any credential. + +=== "Local execution" + + ```python + from seatable_api import Base # (1)! + + API_TOKEN = 'c3c75dca2c369848455a39f4436147639cf02b2d' # (2)! + SERVER_URL = 'https://cloud.seatable.io' + + base = Base(API_TOKEN, SERVER_URL) + base.auth() + ``` + + 1. No need to import `context` here as it won't actually be available. + + 2. This is for demonstration purpose only: try to avoid exposing your credentials directly in the code! Use environment variables or `.venv` files instead. + +It is even possible to develop a Python script in the way that it could be [executed both in the cloud and locally](/scripts/python/common_questions/#how-to-make-the-script-support-both-local-and-cloud-run) without changing the code. + +### Authorization with account object + +Instead of using an API token, you can also authenticate using the `account` object. Doing so, you'll have to provide both your `username` and `password` (in addition to the `server_url` variable). + +Whereas the API token is specific to a base, the `account` object is general and gives you access to all your bases (as when you log on SeaTable). To get a specific base, you'll have to use the `get_base` function, given the workspace ID `workspace_id` and the name of the base `base_name`. To get the workspace ID: + +1. Go to the SeaTable home page. + +2. Click the base whose workspace ID you want to determine. + +3. When the selected base has opened, you can read the Workspace ID at the top of the page URL, which actually looks like *https://cloud.seatable.io/workspace/`84254`/dtable/MyBase* (or any `server_url` instead of *https://cloud.seatable.io*). + + +```python +from seatable_api import Account +account = Account(username, password, server_url) +account.auth() +base = account.get_base(workspace_id, base_name) +``` + +### Authorization expiration handling + +!!! info "This feature works with SeaTable version 3.1+" + +In some cases, the program needs to run for a (very) long time, the code of base operations usually being located in a `while` or `for` loop. . In this case, authorization may expire during execution and cause the program to break. We provide an exception called `AuthExpiredError` that can be caught for reauthorization. + +```python +from seatable_api import Base, context +from seatable_api.exception import AuthExpiredError + +server_url = context.server_url or 'https://cloud.seatable.io' +api_token = context.api_token or 'c3c75dca2c369849455a39f4436147639cf02b2d' + +base = Base(api_token, server_url) +base.auth() + +while True: # (1)! + try: + base.append_row('Table1', {"xxx":"xxx"}) + ... + except AuthExpiredError: + base.auth() +``` + +1. Always be careful with infinite loops! + +## Base operations limits + +As Python scripts are tailored for huge base manipulations and because they actually rely on the [SeaTable API](https://api.seatable.com), you might encounter [Rate](https://api.seatable.com/reference/limits#general-rate-limits) or [Size](https://api.seatable.com/reference/limits#size-limits) limits if you are not vigilant. Here are few tips to avoid reaching the limits: + +- Be always careful with operations in `for` or `while` loops (ensure the ending conditions will be reached) + +- Use *batch* operations as often as possible. Replace for example several `base.append_row` calls with a single `base.batch_append_rows` call. Here are the main batch function: + + - `base.batch_append_rows` + - `base.batch_update_rows` + - `base.batch_delete_rows` + - `base.batch_update_links` + +- Learn more about [lower your calls](https://seatable.com/api-optimization/) diff --git a/docs/scripts/python/objects/accounts.md b/docs/scripts/python/objects/accounts.md index 1242e6c1..2d65c1da 100644 --- a/docs/scripts/python/objects/accounts.md +++ b/docs/scripts/python/objects/accounts.md @@ -1,68 +1,258 @@ -# Python Objects: Account +# Accounts -Account provides an interface to list all Workspaces, add/copy/delete Bases, and obtain access rights to a Base. +The account object provides an interface to list workspaces, add/copy/delete bases, and obtain access rights to a base. -!!! tip "Separate Authentication required" +!!! info "Specific authentication required" - Account requires a separate authentication. + Accessing the account object requires a specific authentication. - ``` - from seatable_api import Account - username = 'xiongxxx@xxx.com' + ``` python + from seatable_api import Account # (1)! + username = 'xxx@email.com' # (2)! password = 'xxxxxxx' - server_url = 'https://cloud.seatable.cn/' + server_url = 'https://cloud.seatable.io/' account = Account(username, password, server_url) account.auth() ``` -## List of account objects + 1. Don't forget to import `Account` from `seatable_api` + + 2. Always be vigilant when exposing your credentials in a script! Prefer as often as possible more secure solutions such as environment variables or \.venv\ files + +## Manage workspaces -!!! abstract "List workspaces" +!!! abstract "list_workspaces" Get all your workspaces and its Bases. ``` python account.list_workspaces() ``` + __Output__ Dict with a single `workspace_list` key containing a list of every workspaces and for each a list of tables or shared tables of views + + __Example__ + + === "Function call" + + ``` python + import json + from seatable_api import Account + username = 'xxx@email.com' + password = 'xxxxxxx' + server_url = 'https://cloud.seatable.io/' + account = Account(username, password, server_url) + account.auth() + workspaces = account.list_workspaces() + print(json.dumps(workspaces, indent=' ')) + ``` + + === "Output example" -!!! abstract "Add a base" + ``` json + { + "workspace_list": [ + { + "id": "", + "name": "starred", /* (1)! */ + "type": "starred", + "table_list": [] + }, + { + "id": "", + "name": "shared", /* (2)! */ + "type": "shared", + "shared_table_list": [], + "shared_view_list": [ + { + "id": 1416, + "dtable_name": "MBase", + "from_user": "b4980649.....b1311ab4ba2@auth.local", + "to_user": "cc7a1d0fcec......df5dcf5b65b99@auth.local", + "permission": "rw", + "table_id": "ji9k", + "view_id": "0000", + "shared_name": "Shared MBase", + "from_user_name": "Tony Stark", + "to_user_name": "Hulk", + "from_user_avatar": "", + "workspace_id": 34996, + "color": null, + "text_color": null, + "icon": null, + "share_id": 1416, + "share_type": "view-share" + } + ], + "share_folders": [] + }, + { + "id": 84254, + "name": "personal", /* (3)! */ + "type": "personal", + "table_list": [ + { + "id": 198299, + "workspace_id": 84254, + "uuid": "0959ee9c-6b....8c-a751-c798431ab3ad", + "name": "AllColumnsBase", + "created_at": "2025-09-04T12:39:08+02:00", + "updated_at": "2025-09-25T11:31:48+02:00", + "color": null, + "text_color": null, + "icon": null, + "is_encrypted": false, + "in_storage": true, + "starred": false + }, + { + "id": 200036, + "workspace_id": 84254, + "uuid": "30fd2a69-07.....e-85ee-be3230a87ea2", + "name": "Big Data", + "created_at": "2025-09-11T12:11:58+02:00", + "updated_at": "2025-09-23T11:09:09+02:00", + "color": null, + "text_color": null, + "icon": null, + "is_encrypted": false, + "in_storage": true, + "starred": false + }, + { + "id": 202730, + "workspace_id": 84254, + "uuid": "98e53b22-80....d5-92ca-c44d783d9561", + "name": "Ledger", + "created_at": "2025-09-23T15:19:30+02:00", + "updated_at": "2025-09-23T17:06:48+02:00", + "color": "#E91E63", + "text_color": null, + "icon": "icon-dollar", + "is_encrypted": false, + "in_storage": true, + "starred": false + }, + { + "id": 197691, + "workspace_id": 84254, + "uuid": "4b5ef925-c178-4000-89e2-941aa65cc747", + "name": "Test", + "created_at": "2025-09-03T09:03:57+02:00", + "updated_at": "2025-09-25T10:37:13+02:00", + "color": "#656463", + "text_color": null, + "icon": "icon-research", + "is_encrypted": false, + "in_storage": true, + "starred": false + } + ], + "folders": [] + }, + { + "id": 86760, + "name": "My group", + "type": "group", + "group_id": 10339, + "group_owner": "cc7a1d0fcec......df5dcf5b65b99@auth.local", + "is_admin": true, + "table_list": [ + { + "id": 197108, + "workspace_id": 86760, + "uuid": "eec7ff7b-638......4cb-315489bca05e", + "name": "My grouped table", + "created_at": "2025-09-01T11:44:49+02:00", + "updated_at": "2025-09-01T11:44:49+02:00", + "color": null, + "text_color": null, + "icon": null, + "is_encrypted": false, + "in_storage": true, + "starred": false + } + ], + "group_shared_dtables": [], + "group_shared_views": [], + "folders": [] + } + ] + } + ``` - Add a base to a Workspace. + 1. "Favorites" section + + 2. "Shared with me" section + + 3. "My bases" section + +## Manage bases + +!!! abstract "get_base" + + Get the base named `base_name` in the workspace whose id is `workspace_id`. You'll be able to interact with this base using all the `base` methods presented in this manual. Please note that the base is authorized. ``` python - account.add_base(name, workspace_id=None) + account.get_base(workspace_id, base_name) ``` + __Output__ base object (throws an error if no workspace with id `workspace_id` or no base `base_name` exists, or if you encounter permission issue) + __Example__ ``` python - account.add_base('new-base', 35) + from seatable_api import Account + username = 'xxx@email.com' + password = 'xxxxxxx' + server_url = 'https://cloud.seatable.io/' + account = Account(username, password, server_url) + account.auth() + base = account.get_base(35, 'new-base') + print(base.get_metadata()) ``` -!!! abstract "Copy a base" +!!! abstract "add_base" - Copy a base to a workspace. + Add a base named `base_name` to a Workspace. If no `workspace_id` is provided, the base will be created in the "My bases" section (workspace named "personal"). ``` python - account.copy_base(src_workspace_id, base_name, dst_workspace_id) + account.add_base(base_name, workspace_id=None) ``` + __Output__ Dict containing the same base metadata as members of the `table_list` of the workspace metadata (throws an error if no workspace with id `workspace_id` exists or if a base named `base_name` already exists in the workspace) + __Example__ ``` python - account.copy_base(35, 'img-file', 74) + from seatable_api import Account + username = 'xxx@email.com' + password = 'xxxxxxx' + server_url = 'https://cloud.seatable.io/' + account = Account(username, password, server_url) + account.auth() + base_metadata = account.add_base('My New Base', 35) + print(base_metadata) ``` -!!! abstract "Get a base" +!!! abstract "copy_base" - Get a base object. Get the Base object named base_name that exists in the workspace whose id is workspace_id. + Copy the base base_name from the workspace whose id is `src_workspace_id` to the workspace whose id is `dst_workspace_id`. ``` python - account.get_base(workspace_id, base_name) + account.copy_base(src_workspace_id, base_name, dst_workspace_id) ``` + __Output__ Dict containing the same base metadata as members of the `table_list` of the workspace metadata (throws an error if no workspace with id `workspace_id` exists or if a base named `base_name` already exists in the workspace) for the newly created base + __Example__ ``` python - base = account.get_base(35, 'new-base') + from seatable_api import Account + username = 'xxx@email.com' + password = 'xxxxxxx' + server_url = 'https://cloud.seatable.io/' + account = Account(username, password, server_url) + account.auth() + base_metadata = account.copy_base(35, 'My Base', 74) + print(base_metadata) ``` diff --git a/docs/scripts/python/objects/big_data.md b/docs/scripts/python/objects/big_data.md index f57a82ca..8fe9d54a 100644 --- a/docs/scripts/python/objects/big_data.md +++ b/docs/scripts/python/objects/big_data.md @@ -1,6 +1,8 @@ # Big data storage -!!! question "Insert rows into big data storage" +## Insert rows into big data storage + +!!! abstract "big_data_insert_rows" Batch insert rows into big data storage. @@ -8,6 +10,8 @@ base.big_data_insert_rows(table_name, rows_data) ``` + __Output__ Dict containing a single `inserted_row_count` key with the number of rows actually inserted in the big data storage. + __Example__ ``` python diff --git a/docs/scripts/python/objects/columns.md b/docs/scripts/python/objects/columns.md index 4f870c49..61e34c3c 100644 --- a/docs/scripts/python/objects/columns.md +++ b/docs/scripts/python/objects/columns.md @@ -1,68 +1,146 @@ -# Column +# Columns + +You'll find below all the available methods to interact with the columns of a SeaTable table. + +{% + include-markdown "includes.md" + start="" + end="" +%} + +## ColumnTypes constants + +!!! info "ColumnTypes" + + When you want to insert/add a column or change a column type, you will need to use these `ColumnTypes`. + + ```python + from seatable_api.constants import ColumnTypes # (1)! + + ColumnTypes.NUMBER # number + ColumnTypes.TEXT # text + ColumnTypes.LONG_TEXT # long text + ColumnTypes.CHECKBOX # checkbox + ColumnTypes.DATE # date & time + ColumnTypes.SINGLE_SELECT # single select + ColumnTypes.MULTIPLE_SELECT # multiple select + ColumnTypes.IMAGE # image + ColumnTypes.FILE # file + ColumnTypes.COLLABORATOR # collaborator + ColumnTypes.LINK # link to other records + ColumnTypes.FORMULA # formula + ColumnTypes.CREATOR # creator + ColumnTypes.CTIME # create time + ColumnTypes.LAST_MODIFIER # last modifier + ColumnTypes.MTIME # modify time + ColumnTypes.GEOLOCATION # geolocation + ColumnTypes.AUTO_NUMBER # auto munber + ColumnTypes.URL # URL + ``` -Every table in a base contains columns. The following calls are available to interact with the columns of a table. + 1. Don't forget this particular import to use `ColumnTypes`! -## List columns +## Get Column(s) -!!! question "List columns" +!!! abstract "get_column_by_name" - List all rows of the table/view. + Get the column of the table `table_name`, given the column name `column_name`. ``` python - base.list_columns(table_name, view_name=None); + base.get_column_by_name(table_name, column_name) ``` - __Example__ + __Output__ Single column dict (`None` if no column named `column_name` exists, throws an error if no table named `table_name` exists) + __Example__ + ``` python - base.list_columns('Table1', default) + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + column = base.get_column_by_name('Table1', 'Name') + print(column) ``` -!!! question "Get column by name" +!!! abstract "list_columns" + Get the columns of a table (specified by its name `table_name`), optionally from a specific view (specified by its name `view_name`). ``` python - base.get_column_by_name(table_name, colume_name) + base.list_columns(table_name, view_name=None) ``` - __Example__ + __Output__ List of column dicts (throws an error if no table named `table_name` exists or if no view named `view_name` exists) + __Example__ + ``` python - base.get_column_by_name('Table1', 'Name') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + columns = base.list_columns('Table1', 'Default View') + print(columns) ``` -!!! question "Get columns by type" +!!! abstract "get_columns_by_type" + Get all the columns of a specific `column_type` in the table `table_name`. See the [ColumnTypes constants](#columntypes-constants) above or the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for more information about supported column types. ``` python base.get_columns_by_type(table_name, column_type) ``` + __Output__ List of column dicts (eventually empty; throws an error if no table named `table_name` exists or if `column_type` is not a valid `ColumnTypes` member) + __Example__ ``` python + from seatable_api import Base, context from seatable_api.constants import ColumnTypes - base.get_columns_by_type('Table1', ColumnTypes.TEXT) + + base = Base(context.api_token, context.server_url) + base.auth() + columns = base.get_columns_by_type('Table1', ColumnTypes.TEXT) + print(columns) ``` ## Insert column -!!! question "Insert column" +!!! abstract "insert_column" - Insert or append a column to a table. + Insert (inside the table) or append (at the end of the table) a column named `column_name` to the table `table_name`. ``` python - base.insert_column(table_name, column_name, column_type, column_key=None, column_data=None) + base.insert_column(table_name, column_name, column_type, column_key=None, column_data=None) # (1)! ``` - __Examples__ + 1. `column_type`: See the [ColumnTypes constants](#columntypes-constants) above or the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for more information about supported column types + + `column_key` (optional): argument specifying the key of the *anchor* column for the insertion (the newly created column will appear just to the right of the *anchor* column) + + `column_data` (optional): For some particular `ColumnTypes`, specific column data may be provided in the `column_data` dict. See the [column data](#column-data) above for more information. + + __Output__ Single column dict (throws an error if no table named `table_name` exists, if a column named `column_name` already exists or if `column_type` is not a valid `ColumnTypes` member) + __Example__ + ``` python - base.insert_column('Table1', 'New long text column', 'long text') + from seatable_api.constants import ColumnTypes + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.insert_column('Table1', 'New long text column', ColumnTypes.LONG_TEXT) ``` ``` python from seatable_api.constants import ColumnTypes + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.insert_column('Table1', 'Link', ColumnTypes.LINK, column_data={ 'table':'Table1', 'other_table':'Test_User' @@ -71,81 +149,177 @@ Every table in a base contains columns. The following calls are available to int ## Rename column -!!! question "Rename column" +!!! abstract "rename_column" - Rename a column. + Rename the column in the table `table_name` whose key is `column_key` with the new name `new_column_name`. Please ensure that you choose a `new_column_name` that doesn't already exists in your table `table_name`. ``` python base.rename_column(table_name, column_key, new_column_name) ``` + __Output__ Single column dict (throws an error if no table named `table_name` exists or if no column with the key `column_key` exists) + __Example__ + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.rename_column('Table1', '0000', 'new column name') # (1)! + ``` + + 1. `0000` is always the key of the first column in each table ``` python - base.rename_column('Table1', 'kSiR', 'new column name') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + column_to_rename = base.get_column_by_name('Table1', 'My Column') + base.rename_column('Table1', column_to_rename['key'], 'new column name') # (1)! ``` -## Freeze column + 1. Accessing the `key` value of a column you just retrieved (for example with `base.get_column_by_name`), you don't have to explicitly know its `column_key` + +## (Un)freeze column -!!! question "Freeze column" +!!! abstract "(Un)freeze_column" - Freeze a column. + Freeze ([fix](https://seatable.com/help/adjust-frozen-columns-seatable/)) or unfreeze the column of table `table_name` whose key is `column_key`. + + !!! warning "(Un)freezing a group of columns" + Please note that this method acts on a single column: to freeze the n-first left columns, please run it **for each column!** ``` python - base.freeze_column(table_name, column_key, frozen) + base.freeze_column(table_name, column_key, frozen) # (1)! ``` - __Example__ + 1. `column_key`: the key of the column you want to (un)freeze + + `frozen`: `True` to freeze, `False` to unfreeze + + __Output__ Single column dict (throws an error if no table named `table_name` exists or if no column with the key `column_key` exists) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.freeze_column('Table1', '0000', True) ``` ## Move column -!!! question "Move column" +!!! abstract "move_column" - Move column. In this example, the column with the key `loPx` will be moved to the right of the column `0000`. + Move the column of table `table_name` whose key is `column_key`. ``` python - base.move_column(table_name, column_key, target_column_key) + base.move_column(table_name, column_key, target_column_key) # (1)! ``` - __Example__ + 1. `column_key`: the key of the column you want to move + + `target_column_key`: the key of the *anchor* column for the move (the column whose key is `column_key` will be moved just to the right of the *anchor* column) + __Output__ Single column dict (throws an error if no table named `table_name` exists or if no column with the key `column_key` or `target_column_key` exists) + + __Example__ + ``` python - base.move_column('Table1', 'loPx', '0000') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.move_column('Table1', 'loPx', '0000') # (1)! ``` -## Modify column types + 1. In this example, the column with the key `loPx` will be moved to the right of the column `0000` + +## Modify column type + +!!! abstract "modify_column_type" -!!! question "Modify column type" + Change the column type of an existing column of table `table_name` whose key is `column_key`. - Change the column type of an existing column + !!! warning "Don't change column type to ColumnTypes.LINK" + This method doesn't allow to pass column data for the moment. Trying to change the column type to `ColumnTypes.LINK` will then lead to a "broken" column (you won't be able to edit the column's settings) as column data is mandatory for link-type columns. ``` python - base.modify_column_type(table_name, column_key, new_column_type) + base.modify_column_type(table_name, column_key, new_column_type) # (1)! ``` - __Example__ + 1. `column_key` (optional): the key of the column you want to modify the type + + `new_column_type`: See the [ColumnTypes constants](#columntypes-constants) above or the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for more information about supported column types + + __Output__ Single column dict (throws an error if no table named `table_name` exists, if no column with the key `column_key` exists or if `new_column_type` is not a valid `ColumnTypes` member) + __Example__ + ``` python - base.modify_column_type('Table1', 'nePI', 'checkbox') + from seatable_api.constants import ColumnTypes + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.modify_column_type('Table1', 'nePI', ColumnTypes.CHECKBOX) ``` -## Add column options +## Delete column -!!! question "Add column options" +!!! abstract "delete_column" - Used by single-select or multiple-select type columns to add new options. + Delete the column whose key is `column_key` in the table `table_name`. You cannot delete the first column as explained [here](https://seatable.com/help/warum-kann-ich-die-erste-spalte-meiner-tabelle-nicht-loeschen/). ``` python - add_column_options(self, table_name, column, options) + base.delete_column(table_name, column_key) ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists, if no column with the key `column_key` exists or if you try to delete the first column) + __Example__ + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.delete_column('Table1', 'bsKL') + ``` + +## Single- and/or multiple-select columns specific methods + +### Add column options +!!! abstract "add_column_options" + + Used by both "single select" or "multiple select"-type columns to add new options to the column `column_name` of the table `table_name`. + + ``` python + base.add_column_options(table_name, column_name, options) # (1)! + ``` + + 1. `options`: list of option dict containing the following keys: + + - `name`: displayed text of the option + + - `color`: background color of the option (hex code) + + - `textColor`: text color of the option (hex code) + + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists, if no column named `column_name` exists or if `options` is invalid) + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.add_column_options('Table1', 'My choices', [ {"name": "ddd", "color": "#aaa", "textColor": "#000000"}, {"name": "eee", "color": "#aaa", "textColor": "#000000"}, @@ -153,38 +327,40 @@ Every table in a base contains columns. The following calls are available to int ]) ``` -## Add column cascade settings +### Add column cascade settings -!!! question "Add column cascade settings" +!!! abstract "add_column_cascade_settings" - Used by single-select column, to add a limitation of child column options according to the option of parent column. + Used by "single select"-type column, to condition the available options (see cascading in the [user manual](https://seatable.com/help/die-einfachauswahl-spalte/#cascading-a-single-select-column-search) or in the [API Reference](https://api.seatable.com/reference/updatecolumncascade-1)) of a child column `child_column` based on the options of a parent column `parent_column`. ``` python - add_column_cascade_settings(table_name, child_column, parent_column, cascade_settings) + base.add_column_cascade_settings(table_name, child_column, parent_column, cascade_settings) # (1)! ``` - __Example__ + 1. `child_column`: name of the column you want to condition the available options for - ``` python - base.add_column_cascade_settings("Table1", "single-op-col-c", "single-op-col", { - "aaa": ["aaa-1", "aaa-2"], # If β€œaaa” is selected by parent column, the available options of child column are "aaa-1 and aaa-2" - "bbb": ["bbb-1", "bbb-2"], - "ccc": ["ccc-1", "ccc-2"] - }) - ``` + `parent_column`: name of the parent column whose options will be used to condition the available options of the child column -## Delete column + `cascade_settings`: cascade dict using the following structure: -!!! question "Delete column" + - each key is the `name` of an option from the parent column - Deletes a column from the table. - - ``` python - base.delete_column(table_name, column_key) - ``` + - each corresponding value is a list of the names of every allowed options from the child column + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists, if no column named `child_column` or `parent_column` exists or if `cascade_settings` is invalid) + __Example__ - + ``` python - base.delete_column('Table1', 'bsKL') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.add_column_cascade_settings("Table1", "Child", "Parent", { + "aaa": ["aaa-1", "aaa-2"], # (1)! + "bbb": ["bbb-1", "bbb-2"], + "ccc": ["ccc-1", "ccc-2"] + }) ``` + + 1. If `aaa` is selected in the parent column, the available options for the child column will be `aaa-1` and `aaa-2` diff --git a/docs/scripts/python/objects/communication-utils.md b/docs/scripts/python/objects/communication-utils.md new file mode 100644 index 00000000..77ff99c3 --- /dev/null +++ b/docs/scripts/python/objects/communication-utils.md @@ -0,0 +1,148 @@ +# Communication utility functions + +Several outgoing communications features are available within SeaTable. Wether you want to communicate with a user in the web interface or or be alerted of database changes from another process, here are the methods you can use while scripting. + + +!!! info "Going further" + Keep in mind that communication methods will probably require other coding skills as they mostly make sense outside of SeaTable. The [API Reference](https://api.seatable.com/reference/getbaseactivitylog-1) also details other methods such as getting base or row activities logs, which might also help you stay informed about what's happening in the base (but without the automatic firing on the SeaTable side of the methods presented here). + +## Toast notifications + +!!! abstract "send_toast_notification" + + Show a toast notification in SeaTable's web interface to a user. The username you have to provide is a unique identifier ending by `@auth.local`. This is **neither** the email address of the user **nor** its name. The content of `msg` is plain text. + + ```python + base.send_toast_notification(username, msg, toast_type='success') # (1)! + ``` + + 1. `toast_type` can be one of `success`, `warning` or `danger` + + __Example__ + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.send_toast_notification( + "aea9e807bcfd4f3481d60294df74f6ee@auth.local", + "error request", + "danger" + ) + ``` + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + # Time to cheer up yourself! + my_username = context.current_username + base.send_toast_notification( + my_username, + "You're doing great!", + "success" + ) + ``` + +## Websockets + +!!! abstract "socketIO" + + By using websocket, you can get __realtime data update notifications__ of a base. + + !!! info "websocket-client library recommended" + + You might encounter the warning `websocket-client package not installed, only polling transport is available` when you run the script below. The library is not required as you'll get the update infos anyway (using polling transport), but installing the websocket-client library will allow you to benefit from a real websocket transport. + + ```python + from seatable_api import Base + + server_url = 'https://cloud.seatable.io' + api_token = 'c3c75dca2c369849455a39f4436147639cf02b2d' + + base = Base(api_token, server_url) + base.auth(with_socket_io=True) # (1)! + + base.socketIO.wait() + ``` + + 1. Note that using websocket needs to specify the argument `with_socket_io=True` as compared to usual authentication + + When the base data is updated, the following will be output in the terminal. + + ```log + 2022-07-19 11:48:37.803956 [ SeaTable SocketIO connection established ] + 2022-07-19 11:48:39.953150 [ SeaTable SocketIO on UPDATE_DTABLE ] + {"op_type":"insert_row","table_id":"0000","row_id":"YFK9bD1XReSuQ7WP1YYjMA","row_insert_position":"insert_below","row_data":{"_id":"RngJuRa0SMGXyiA-SHDiAw","_participants":[],"_creator":"seatable@seatable.com","_ctime":"","_last_modifier":"seatable@seatable.com","_mtime":""},"links_data":{}} + ``` + + After getting data update notifications, perform self-defined actions by listen to a specific event. Available events are `UPDATE_DTABLE` (database update) or `NEW_NOTIFICATION` (new notification received). Please note that we are here talking about SeaTable system's notifications (see the [User manual](https://seatable.com/help/homepage/notifications/) and not about the toast notifications fired by the `base.send_toast_notification` method). + + ```python + import json + from seatable_api import Base + from seatable_api.constants import UPDATE_DTABLE # (1)! + + server_url = 'https://cloud.seatable.io' + api_token = 'c3c75dca2c369849455a39f4436147639cf02b2d' + + def on_update(data, index, *args): + try: + operation = json.loads(data) + print(operation) + op_type = operation['op_type'] + table_id = operation['table_id'] + row_id = operation['row_id'] + # ... do something + except Exception as e: + print(e) + + base = Base(api_token, server_url) + base.auth(with_socket_io=True) + + base.socketIO.on(UPDATE_DTABLE, on_update) # (2)! + base.socketIO.wait() + ``` + + 1. Note that you'll have to import the corresponding event (`UPDATE_DTABLE` or `NEW_NOTIFICATION`) + + 2. First argument is the event triggering the system, second argument is the event handler (the name of the function that will be run when a new event happens) + +## Webhooks + +Another communication feature offered by SeaTable is Webhooks. Webhooks are covered in the [User manual](https://seatable.com/help/integrations/webhooks/) for global understanding and in the [API Reference](https://api.seatable.com/reference/listwebhooks) for webhook handlings functions. + +As SeaTable usually sends a webhook for every change, this might not be fully adapted if you need to track only a few changes. If you want to track only few operations (to trigger a workflow automation process for example), you can create [automation rules](https://seatable.com/help/automations-overview-seatable/) to send, via a Python script, a `POST` request to an incoming webhook, passing, for example, a string to identify the action and the id of the triggering row. + +!!! warning "Enterprise subscription needed" + + Automations are available only with an [Enterprise subscription](https://seatable.com/help/subscription-plans/#seatable-cloud-enterprise-search). + +__Example__ + +In this example, we can imagine setting up a simple automation rule triggered by a record update in a specific column that should trigger an automation process through a webhook. This automation will have a single "Run Python script" action launching the following script. + +```python +import requests +from seatable_api import context + +url = 'https://mywebhookurl.com' +data = {'action': 'transfer', '_id': context.current_row['_id']} # (1)! +try: + response = requests.post(url, json = data) + if response.status_code != 200: + print('Failed request, status code: ', response.status_code) + exit(1) +except Exception as e: + print(e) + exit(1) +print(response.text) # (2)! +``` + +1. You can actually pass whatever data you want in the `data` object. Here, there are two keys: + - `action`: a string parameter allowing to switch processes depending on this parameter to allow one single entry point for several scenarios + - `_id`: we pass the id of the triggering row to be able to use it on the webhook receiver side + +2. Allows you to check if your request was successful or not \ No newline at end of file diff --git a/docs/scripts/python/objects/constants.md b/docs/scripts/python/objects/constants.md deleted file mode 100644 index abdeed72..00000000 --- a/docs/scripts/python/objects/constants.md +++ /dev/null @@ -1,33 +0,0 @@ -# Constants - -In the script there may be some constants we need to know - -## ColumnTypes - -!!! question "ColumnTypes" - - Column type, when insert/add columns, change column types, etc. need to be used - - ```python - from seatable_api.constants import ColumnTypes - - ColumnTypes.NUMBER # number - ColumnTypes.TEXT # text - ColumnTypes.LONG_TEXT # long text - ColumnTypes.CHECKBOX # checkbox - ColumnTypes.DATE # date & time - ColumnTypes.SINGLE_SELECT # single select - ColumnTypes.MULTIPLE_SELECT # multiple select - ColumnTypes.IMAGE # image - ColumnTypes.FILE # file - ColumnTypes.COLLABORATOR # collaborator - ColumnTypes.LINK # link to other records - ColumnTypes.FORMULA # formula - ColumnTypes.CREATOR # creator - ColumnTypes.CTIME # create time - ColumnTypes.LAST_MODIFIER # last modifier - ColumnTypes.MTIME # modify time - ColumnTypes.GEOLOCATION # geolocation - ColumnTypes.AUTO_NUMBER # auto munber - ColumnTypes.URL # URL - ``` diff --git a/docs/scripts/python/objects/context.md b/docs/scripts/python/objects/context.md index 16d36c4a..164bc4a9 100644 --- a/docs/scripts/python/objects/context.md +++ b/docs/scripts/python/objects/context.md @@ -1,6 +1,14 @@ # Context -When the script is running in the cloud, the context object provides a context environment. Here's how to use it +When the script is running in the cloud, the context object provides a context environment. Here's how to use it. + +!!! warning "Function import required" + + To use these functions, the context module must be imported. + + ``` + from seatable_api import context + ``` ## server_url @@ -40,7 +48,7 @@ When the script is running in the cloud, the context object provides a context e !!! info "current_table" - The name of the table that the current user is viewing when the user runs a script manually. + The name of the table that the current user is viewing when the script is run. ``` python context.current_table @@ -57,7 +65,11 @@ When the script is running in the cloud, the context object provides a context e !!! info "current_row" - When the user manually runs a script, the line where the cursor is currently located. + The line which triggered the script run: + + - the line where the cursor is currently located (if the script is run manually) + - the line from which the button to launch the script was clicked (if the script is run from a button-type column click) + - each line triggering the automation (if the script is run by an automation rule) ``` python context.current_row @@ -74,7 +86,7 @@ When the script is running in the cloud, the context object provides a context e !!! info "current_username" - The System ID of the user who runs the script manually (in old verison, it is called current_user_id). + The system ID of the user who runs the script manually (it was previously called `current_user_id`). It is a unique identifier ending by `@auth.local`. ``` python context.current_username @@ -91,7 +103,7 @@ When the script is running in the cloud, the context object provides a context e !!! info "current_id_in_org" - The id of the user in the team, it can be set by the team admin via Web UI. + The id of the user in the team, it can be set by the team admin via the web interface. ``` python context.current_id_in_org diff --git a/docs/scripts/python/objects/date-utils.md b/docs/scripts/python/objects/date-utils.md index 1c914781..74b9a4d7 100644 --- a/docs/scripts/python/objects/date-utils.md +++ b/docs/scripts/python/objects/date-utils.md @@ -1,8 +1,8 @@ -# Date Utility Functions +# Date utility functions -We provide a set of functions for the date operations based on the datetime module of python. These functions have the same behavior as the functions provided by the formula column of SeaTable. +We provide a set of functions for the datetime (date and time) operations based on the datetime python library. These functions have the same behavior as the functions provided by the formula column of SeaTable. -!!! warning "function import required" +!!! warning "Function import required" To use these functions, the dateutils module must be imported. @@ -10,13 +10,54 @@ We provide a set of functions for the date operations based on the datetime modu from seatable_api.date_utils import dateutils ``` -!!! tip "Timezone" +## Introduction +### Date and time formatting + +The ISO format is used in date methods, both for input and output, which means: + +- `YYYY-MM-DD` (or `%Y-%m-%d` referring to the [python datetime library format codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)) for dates + +- `YYYY-MM-DD HH:mm:ss` (or `%Y-%m-%d %H:%M:%S`) for datetimes. Please note that the hour is (24-hour based) + +- Datetimes format with timezone info requires a specific format: `YYYY-MM-DDTHH:mm:ssΒ±hh:mm` with the letter `T` separating the date from the time and `Β±hh:mm` representing the offset to UTC (here `+08:00` for UTC+8) + +Of course, methods outputs with this format can be reused as input for other `dateutils` methods requiring the same format. You'll find below an overview example. Every methods are detailed in the following of this section. + +!!! info "Timezone" + If the input time string has a timezone info, it will be automatically converted to local time. -## date +### Overview example + +In this example as in the following of this section, the comment at the end of each line shows the expected result (what you should update if you `print` the result of the current line) + +``` python +from seatable_api.date_utils import dateutils + +dt_now = dateutils.now() # 2025-09-30 15:47:00 +# 1. date 10 days after dt_now +dt_10_days = dateutils.dateadd(dt_now, 10) # 2025-10-10 15:47:00 +# 2. month 10 days after dt_now +dt_month_10_days = dateutils.month(dt_10_days) # 10 +# 3. difference between 2 days +dt_10_days_before = dateutils.dateadd(dt_now, -10) +date_df = dateutils.datediff(dt_10_days_before, dt_10_days, unit="D") # 20 +# 4. handle the time string with time-zone info with local timezone of "Asia/Shanghai" (UTC+8) +time_str = "2025-07-17T21:57:41+08:00" +time_day = dateutils.day(time_str) # 17 +time_month = dateutils.month(time_str) # 7 +time_year = dateutils.year(time_str) # 2025 +time_hour = dateutils.hour(time_str) # 15 (! if local timezone is UTC+2 !) +time_minute = dateutils.minute(time_str) # 57 +time_date = dateutils.date(time_year, time_month, time_day) # 2025-07-17 +``` + +## Dealing with date and time -!!! success "date" +### date + +!!! abstract "date" Return the ISO formatted date string. @@ -25,454 +66,525 @@ We provide a set of functions for the date operations based on the datetime modu ``` __Example__ - + ``` python - custom_date = dateutils.date(2020, 5, 16) - print(custom_date) # 2020-05-16 + from seatable_api.date_utils import dateutils + + custom_date = dateutils.date(2025, 9, 16) + print(custom_date) # 2025-09-16 ``` -## now +### dateadd -!!! success "now" +!!! abstract "dateadd" - Return the ISO formatted date time of current and accurated to seconds. + Add a `number` of a specified `interval` to a datetime `datetime_str`. `interval` can represent the following units: `years`, `months`, `weeks`, `days`, `hours`, `minutes` and `seconds` (default is `days`). Negative values ​​can be used to subtract from `datetime_str`. ``` python - dateutils.now() + dateutils.dateadd(datetime_str, number, interval) ``` __Example__ - + ``` python - now = dateutils.now() - print(now) # 2022-02-07 09:44:00 + from seatable_api.date_utils import dateutils + + date_str = "2025-9-15" + datetime_str = "2025-9-15 15:23:21" + + dateutils.dateadd(date_str, -2, 'years') # 2023-09-15 + dateutils.dateadd(date_str, 3, 'months') # 2025-12-15 + dateutils.dateadd(datetime_str, 44, 'minutes') # 2025-09-15 16:07:21 + dateutils.dateadd(datetime_str, 1000, 'days') # 2028-06-11 15:23:21 + dateutils.dateadd(datetime_str, 3, 'weeks') # 2025-10-06 15:23:21 + dateutils.dateadd(datetime_str, -3, 'hours') # 2025-09-15 12:23:21 + dateutils.dateadd(datetime_str, 3, 'seconds') # 2025-09-15 15:23:24 ``` -## today +### datediff -!!! success "today" +!!! abstract "datediff" - Return the ISO formatted current date time in string + Compute the time between two datetimes in one of the following units:`S`, `Y`, `D`, `H`, `M`, `YM`, `MD`, `YD`. The result can be negative if `end_date` is before `start_date`. + + For date units (`Y`,`M` and `D`), unit might include two characters: + + - `YM`: The difference between the months in `start_date` and `end_date`. The days and years of the dates are ignored. + - `MD`: The difference between the days in `start_date` and `end_date`. The months and years of the dates are ignored. + - `YD`: The difference between the days of `start_date` and `end_date`. The years of the dates are ignored. ``` python - dateutils.today() + dateutils.datediff(start=start_date, end=end_date, unit=datetime_unit) + dateutils.datediff(start_date, end_date, datetime_unit) # (1)! ``` - __Example__ + 1. Depending on your preferences, you can either specify the name of each parameter (longer but easier to reread) or not + __Example__ + ``` python - today = dateutils.today() - print(today) # 2022-02-07 + from seatable_api.date_utils import dateutils as dt # (1)! + + start_date = "2025-5-16" + end_date = "2026-5-15" + + dt.datediff(start=start_date, end=end_date, unit='S') # 31449600 (seconds) + dt.datediff(start=start_date, end=end_date, unit='Y') # 0 (years) + dt.datediff(start=start_date, end=end_date, unit='D') # 364 (days) + dt.datediff(start=start_date, end=end_date, unit='H') # 8736 (hours) + dt.datediff(start=start_date, end=end_date, unit='M') # 12 (months) (from 2025-5 to 2026-5) + dt.datediff(start=start_date, end=end_date, unit='YM') # 0 (months) (from May to May) + dt.datediff(start=start_date, end=end_date, unit='MD') # -1 (days) (from 16 of 15) + dt.datediff("2025-1-28","2026-2-1", unit='YD') # 4 (days) (from January 28 to February 1) ``` -## dateadd + 1. To make calls shorter or more explicit, feel free to use an alternative name using `as` keyword. Here, we use `dt` instead of the default `dateutils` + +### day -!!! success "dateadd" +!!! abstract "day" - Addition operation for a datetime by different units such as years, months, weeks, days, hours, minutes and seconds, default by days. + Return the day of a given `date`. ``` python - dateutils.dateadd(time_str, number, inverval) + dateutils.day(date) ``` __Example__ - + ``` python - time_str = "2020-6-15" - time_str_s = "2020-6-15 15:23:21" - - dateutils.dateadd(time_str, -2, 'years') # 2018-06-15 - dateutils.dateadd(time_str, 3, 'months') # 2020-09-15 - dateutils.dateadd(time_str_s, 44, 'minutes') # 2020-06-15 16:07:21 - dateutils.dateadd(time_str_s, 1000, 'days') # 2023-03-12 15:23:21 - dateutils.dateadd(time_str_s, 3, 'weeks') # 2020-07-06 15:23:21 - dateutils.dateadd(time_str_s, -3, 'hours') # 2020-06-15 12:23:21 - dateutils.dateadd(time_str_s, 3, 'seconds') # 2020-06-15 15:23:24 + from seatable_api.date_utils import dateutils + + dateutils.day('2025-6-15 14:23:21') # 15 ``` -## datediff +### days -!!! success "datediff" +!!! abstract "days" - Caculation of the different between 2 date times by different units such as S, Y, D, H, M, YM, MD, YD. - - - __YM__: The difference between the months in start_date and end_date. The days and years of the dates are ignored. - - __MD__: The difference between the days in start_date and end_date. The months and years of the dates are ignored. - - __YD__: The difference between the days of start_date and end_date. The years of the dates are ignored. + Return the days difference between two given date. The result can be negative if `end` is before `start`. ``` python - dateutils.datediff(start, end, unit) + dateutils.days(start, end) ``` __Example__ - + ``` python - time_start = "2019-6-1" - time_end = "2020-5-15" - dateutils.datediff(start=time_start, end=time_end, unit='S') # seconds 30153600 - dateutils.datediff(start=time_start, end=time_end, unit='Y') # years 0 - dateutils.datediff(start=time_start, end=time_end, unit='D') # days 349 - dateutils.datediff(start=time_start, end=time_end, unit='H') # hours 8376 - dateutils.datediff(start=time_start, end=time_end, unit='M') # months 11 - dateutils.datediff(start=time_start, end=time_end, unit='YM') # 11 - dateutils.datediff(start=time_start, end=time_end, unit='MD') # 14 - dateutils.datediff("2019-1-28","2020-2-1", unit='YD') # 3 + from seatable_api.date_utils import dateutils + + dateutils.days('2024-6-1', '2025-5-15') # 348 ``` -## eomonth +### eomonth -!!! success "eomonth" +!!! abstract "eomonth" - Return the last day of n months befor or after given date. Parameter months refers to n. + Return the ISO formatted last day of the `n`th month before or after given date (depending on the sign of `n`). ``` python - dateutils.eomonth(date, months) + dateutils.eomonth(date, months=n) ``` __Example__ - + ``` python - date = "2022-7-4" - dateutils.eomonth(date, months=0) # 2022-07-31 - dateutils.eomonth(date, months=2) # 2022-09-30 - dateutils.eomonth(date, months=-5) # 2022-02-28 + from seatable_api.date_utils import dateutils + + date = "2025-7-4" + dateutils.eomonth(date, months=0) # 2025-07-31 (months=0 => current month) + dateutils.eomonth(date, months=2) # 2025-09-30 (2 months after July => September) + dateutils.eomonth(date, months=-5) # 2025-02-28 (5 months before July => February) ``` -## year +### hour -!!! success "year" +!!! abstract "hour" - Return the year of given date. + Return the hour of a given `datetime`. ``` python - dateutils.year(date) + dateutils.hour(datetime) ``` __Example__ - + ``` python - dateutils.year("2019-1-1") # 2019 + from seatable_api.date_utils import dateutils + + dateutils.hour("2025-1-1 12:13:14") # 12 ``` -## month +### hours -!!! success "month" +!!! abstract "hours" - Return the month of given date. + Return the hours difference between two given datetime. The result can be negative if `end` is before `start`. ``` python - dateutils.month(date) + dateutils.hours(start, end) ``` __Example__ - + ``` python - dateutils.month("2019-5-4") # 5 + from seatable_api.date_utils import dateutils + + dateutils.hours("2019-6-3 20:01:12", "2020-5-3 13:13:13") # 8009 ``` -## months +### minute -!!! success "months" +!!! abstract "minute" - Return the months difference of two given date. + Return the minutes of a given `datetime`. ``` python - dateutils.months(start, end) + dateutils.minute(datetime) ``` __Example__ - + ``` python - dateutils.months("2019-5-1","2020-5-4") # 12 + from seatable_api.date_utils import dateutils + + dateutils.minute("2025-5-3 13:14:15") # 13 ``` -## day +### month -!!! success "day" +!!! abstract "month" - Return the day of given date. + Return the month of a given `date`. The month number starts at 1, like when writing a date. ``` python - dateutils.day(date) + dateutils.month(date) ``` __Example__ - + ``` python - dateutils.day('2020-6-15 14:23:21') # 15 + from seatable_api.date_utils import dateutils + + dateutils.month("2025-5-4") # 5 ``` -## days +### isomonth -!!! success "days" +!!! abstract "isomonth" - Return the days difference of two given date. + Return the ISO formatted (`YYYY-MM`) month of a given `date`. ``` python - dateutils.days(start, end) + dateutils.isomonth(date) ``` __Example__ - + ``` python - dateutils.days('2019-6-1', '2020-5-15') # 349 + from seatable_api.date_utils import dateutils + + dateutils.isomonth("2025-1-2") # 2025-01 ``` -## hour +### months -!!! success "hour" +!!! abstract "months" - Return the hour of given datetime. + Return the months difference between two given date. The result can be negative if `end` is before `start`. ``` python - dateutils.hour(date) + dateutils.months(start, end) ``` __Example__ - + ``` python - dateutils.hour("2020-1-1 12:20:30") # 12 - ``` + from seatable_api.date_utils import dateutils -## hours + dateutils.months("2024-5-1","2025-5-4") # 12 + ``` + +### now -!!! success "hours" +!!! abstract "now" - Return the hours difference of two given datetime. + Return the ISO formatted current date and time,accurate to seconds. ``` python - dateutils.hours(start, end) + dateutils.now() ``` __Example__ - + ``` python - dateutils.hours("2019-6-3 20:1:12", "2020-5-3 13:13:13") # 8033 + from seatable_api.date_utils import dateutils + + now = dateutils.now() + print(now) # 2025-09-30 12:56:41 ``` -## minute +### second -!!! success "minute" +!!! abstract "second" - Return the minutes of given datetime. + Return the seconds of given datetime. ``` python - dateutils.minute(date) + dateutils.second(date) ``` __Example__ - + ``` python - dateutils.minute("2020-5-3 13:13:13") # 13 + from seatable_api.date_utils import dateutils + + dateutils.second("2025-5-3 13:13:33") # 33 ``` -## second +### today -!!! success "second" +!!! abstract "today" - Return the seconds of given datetime. + Return the ISO formatted current date time in string ``` python - ateutils.second(date) + dateutils.today() ``` __Example__ - + ``` python - ateutils.second("2020-5-3 13:13:33") # 33 + from seatable_api.date_utils import dateutils + + today = dateutils.today() + print(today) # 2025-09-30 ``` -## weekday +### weekday -!!! success "weekday" +!!! abstract "weekday" - Return the weekday by recording 0 to 6 from Monday to Sunday. + Return the weekday of a `date`. The result (from 0 to 6) consider a week starting on Monday (returns 0) and ending on Sunday (returns 6). ``` python dateutils.weekday(date) ``` __Example__ - + ``` python - dateutils.weekday("2019-6-3") # 0 + from seatable_api.date_utils import dateutils + + dateutils.weekday("2025-6-2") # 0 (June 2,2025 was a Monday) ``` -## isoweekday +### isoweekday -!!! success "isoweekday" +!!! abstract "isoweekday" - Return the weekday by recording 1 to 7 from Monday to Sunday based on ISO standard. + Return the weekday of a `date` from 1 to 7 and considering a week starting on Monday (based on ISO standard). ``` python dateutils.isoweekday(date) ``` __Example__ - + ``` python - dateutils.isoweekday("2019-6-3") # 1 + from seatable_api.date_utils import dateutils + + dateutils.isoweekday("2025-6-2") # 1 ``` -## weeknum +### weeknum -!!! success "weeknum" +!!! abstract "weeknum" - Return the week number of given date by counting the 1st of Jan. as the first week. + Return the week number of a given `date`, considering the week including January 1st as the first week. ``` python dateutils.weeknum(date) ``` __Example__ - + ``` python - dateutils.weeknum('2012-1-2') # 2 + from seatable_api.date_utils import dateutils + + dateutils.weeknum('2027-1-2') # 1 ``` -## isoweeknum +### isoweeknum -!!! success "isoweeknum" +!!! abstract "isoweeknum" - Return the week number of given date based on ISO standard. + Return the week number of a given `date` based on ISO standard. ``` python dateutils.isoweeknum(date) ``` __Example__ - + ``` python - dateutils.isoweeknum('2012-1-2') # 1 + from seatable_api.date_utils import dateutils + + dateutils.isoweeknum('2027-1-2') # 53 ``` -## isomonth +### year -!!! success "isomonth" +!!! abstract "year" - Return the ISO formatted month. + Return the year of a given `date`. ``` python - dateutils.isomonth(date) + dateutils.year(date) ``` __Example__ - + ``` python - dateutils.isomonth("2012-1-2") # 2012-01 + from seatable_api.date_utils import dateutils + + dateutils.year("2030-1-1") # 2030 ``` -## quarter_from_yq +## Dealing with quarters + +A specific DateQuarter object exists to deal with quarters. The operations/properties/methods available this object are presented below. -!!! success "quarter_from_yq" +### quarter_from_yq - Return a DateQuarter object, and params inlclude year and quarter.. +!!! abstract "quarter_from_yq" + + Return a DateQuarter object, from a given `year` and `quarter` (1 to 4 for current year). if `quarter` is n less than 1 (or n greater than 4), the returned DateQuarter object will correspond to the year and quarter shifted by n quarters before the first quarter (or n quarters after the fourth quarter) of the `year`. ``` python dateutils.quarter_from_yq(year, quarter) ``` __Example__ - + ``` python - dateutils.quarter_from_yq(2022, 3) # DateQuarter obj: + from seatable_api.date_utils import dateutils + + dateutils.quarter_from_yq(2025, 3) # DateQuarter obj: + dateutils.quarter_from_yq(2025, 0) # DateQuarter obj: + dateutils.quarter_from_yq(2025, 6) # DateQuarter obj: ``` -## quarter_from_ym +### quarter_from_ym -!!! success "quarter_from_ym" +!!! abstract "quarter_from_ym" - Return a DateQuarter object, and params include year and month. + Return a DateQuarter object, for specified `year` and `month`. ``` python dateutils.quarter_from_ym(year, month) ``` __Example__ - + ``` python - dateutils.quarter_from_ym(2022, 3) # DateQuarter obj: + from seatable_api.date_utils import dateutils + + dateutils.quarter_from_ym(2025, 3) # DateQuarter obj: ``` -## to_quarter +### to_quarter -!!! success "to_quarter" +!!! abstract "to_quarter" - Return a DateQuarter object of a time string. + Return a DateQuarter object from an ISO formatted date or datetime string `datetime_str`. ``` python - dateutils.to_quarter(time_str) + dateutils.to_quarter(datetime_str) ``` __Example__ - + ``` python - dateutils.to_quarter("2022-07-17") # DateQuarter obj: + from seatable_api.date_utils import dateutils + + dateutils.to_quarter("2025-07-17") # DateQuarter obj: ``` -## quarters_within +### quarters_within -!!! success "quarters_within" +!!! abstract "quarters_within" - Return a generator which will generate the DateQuater objects between a start date and end date. You can get the last quarter in the generator if you set param `include_last=True` which is `False` by default. + Return a generator which will generate the DateQuater objects between a `start` date and an `end` date. The last (not necessarily full) quarter isn't included by default. You can get it in the generator if you set param `include_last` to `True` (`False` by default). ``` python - dateutils.quarters_within(start, end, include_last) + dateutils.quarters_within(start, end, include_last=False) ``` __Example__ - + ``` python - qs = dateutils.quarters_within("2021-03-28", "2022-07-17", include_last=True) - list(qs) # [, ,...., ] + from seatable_api.date_utils import dateutils + + qs1 = dateutils.quarters_within("2024-03-28", "2025-07-17") + print(list(qs1)) # [, ,...., ] + + qs2 = dateutils.quarters_within("2024-03-28", "2025-07-17", include_last=True) + print(list(qs2)) # [, ,...., , ] ``` -## Quarter operation +### DateQuarter properties and methods -!!! success "Quarter operation" +Some operations/properties/methods are available for DateQuarter objects. + +!!! abstract "DateQuarter properties and methods" - Some operations are supported based on DateQuater object. Please refer the examples below: + - `year`: The year of the considered DateQuarter + - `quarter`: The quarter of the considered DateQuarter (1 to 4) + - `start_date`: The ISO formatted first day of the considered DateQuarter + - `end_date`: The ISO formatted last day of the considered DateQuarter + - `days()`: A generator, which will generate every dates (`datetime.date` objects) in the considered DateQuarter - ``` python - q = dateutils.quarter_from_yq(2022, 3) + __Example__ - q.year # 2022 - q.quarter # 3 + ```python + from seatable_api.date_utils import dateutils + + q = dateutils.quarter_from_yq(2025, 3) - q.start_date # 2022-07-01 - q.end_date # 2022-09-30 + q.year # 2025 + q.quarter # 3 - q.days() # generator, which will generate the date in such quarter - list(q.days()) # [datetime.date(2022, 7, 1), datetime.date(2022, 7, 2),....., datetime.date(2022, 9, 30)] + q.start_date # 2025-07-01 + q.end_date # 2025-09-30 - q + 10 # - q1 = dateutils.quater_from_yq(2021, 1) # - q - q1 # 6 - q < q1 # False - "2022-6-28" in q # False - "2022-8-28" in q # True + q.days() + list(q.days()) # [datetime.date(2025, 7, 1), datetime.date(2025, 7, 2),....., datetime.date(2025, 9, 30)] ``` -## Other examples +### DateQuarter operations + +!!! abstract "DateQuarter operations" -!!! success "Other examples" + Classical operators are available for DateQuarter objects: - The date info returned can also be assigned as a param of dateutils. Here are some examples: + - **Arithmetic operators**: `+` (adds a number of quarters to a DateQuarter object), `-` (returns the number of quarters between two quarters, or the quarter shifted by the number of quarters if used with a number) + - **Comparison operators**: `<`, `<=`, `==`, `>=`, `>`, `!=` + - **Membership operators**: `in`, `not in` + + + __Example__ ``` python - dt_now = dateutils.now() # 2022-02-07 09:49:14 - # 1. date after 10 days - dt_10_days = dateutils.dateadd(dt_now, 10) # 2022-02-17 09:49:14 - # 2. month after 10 days - dt_month_10_days = dateutils.month(dt_10_days) # 2 - # 3. difference between 2 days - dt_10_days_before = dateutils.dateadd(dt_now, -10) - date_df = dateutils.datediff(dt_10_days_before, dt_10_days, unit="D") # 20 - # 4. handle the time string with time-zone info with local timezone of "Asia/Shanghai" (UTC+8) - time_str = "2021-07-17T08:15:41.106+00:00" - time_day = dateutils.day(time_str) # 17 - time_month = dateutils.month(time_str) # 7 - time_year = dateutils.year(time_str) # 2021 - time_hour = dateutils.hour(time_str) # 16 - time_date = dateuitls.date(time_year, time_month, time_day) # 2021-07-17 + from seatable_api.date_utils import dateutils + + q = dateutils.quarter_from_yq(2026, 3) + + q + 10 # + q1 = dateutils.quarter_from_yq(2025, 1) # + q - q1 # 6 + q - 7 # + q < q1 # False + "2026-28" in q # False + "2026-8-28" in q # True ``` diff --git a/docs/scripts/python/objects/files.md b/docs/scripts/python/objects/files.md index 4bb30d27..7c1bbb64 100644 --- a/docs/scripts/python/objects/files.md +++ b/docs/scripts/python/objects/files.md @@ -2,65 +2,161 @@ ## Download -!!! question "Download (simple method)" +For the following methods, you'll have to provide the URL of the file you want to download. The file URL structure is as follows: - Download a file to a local path. +```js +{server_url}/workspace/{workspace_id}/asset-preview/{base_uuid}/{file location} +``` + +- `{server_url}` is the URL of your server, for example `https://cloud.seatable.io` + +- You can find the `workspace_id` by looking at any of your database URL which will look like `{server_url}/workspace/{workspace_id}/dtable/{base_name}`, or by checking the [user manual](https://seatable.com/help/workspace-id-einer-gruppe-ermitteln/) + +- You can find the base uuid in your Team administration (see the [User manual](https://seatable.com/help/bases-in-der-teamverwaltung/), it's displayed as `ID` in the base right panel) or by looking for `dtableUuid` in the source code of the web page while consulting any of your bases + +- The file location is what you can find in the [file manager](https://seatable.com/help/file-management-in-a-base/) of your base and will always have the same structure: + + - if uploaded automatically, the file will be in the system folder `files` (if uploaded in a **file-type column**, even if it's an image) and in the subdirectory `YYYY-MM` (year and month of the upload), for example `https://cloud.seatable.io/dtable-web/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/files/2020-10/invoice.pdf` is the URL of a file called invoice.pdf and downloaded in October 2020 (`2020-10`) in the base whose uuid is `41cd05da-b29a-4428-bc31-bd66f4600817` in the workspace whose id is `74` on `https://cloud.seatable.io`. It will be the same for an image, but in the `images` system folder instead of the `files` folder (if uploaded in an **image-type column only**) + - if uploaded by yourself in a custom folder, the file will be in directory `custom` and in the eventual directory you created, for example: +`https://cloud.seatable.io/dtable-web/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/custom/My Personal Folder/quote.pdf` is the URL of a file called quote.pdf that you stored in the folder `My Personal Folder` of the custom folders in the same database. + +!!! info "Get easily the file URL" + For files that need to open an external window to preview (almost all files except images), the URL of this new window will actually be the URL your looking for! + +!!! abstract "Download (simple one-step method)" + + Download a file to a local path. The save path as naturally to ends with the same extension as the original file. ``` python base.download_file(file_url, save_path) ``` + __Output__ Nothing (throws an error if the URL is invalid or if the save path is wrong) __Example__ - + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() file_url = "https://cloud.seatable.io/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/files/2020-10/invoice.pdf" save_path = "/tmp/invoice.pdf" base.download_file(file_url, save_path) ``` -!!! question "Download (detailed method)" + Download every file from the *File* column of *Table1* table, providing the id of the row. + + ```python + from seatable_api import Base + + server_url = 'https://cloud.seatable.io' + api_token = '5e165f8b7af...98950b20b022083' + + base = Base(api_token, server_url) + base.auth() + + target_row = base.get_row('Table1','Pd_pHLM8SgiEcnFW5I7HLA') + + save_directory = './tmp/' # (1)! + + files = target_row['File'] + print(f"{len(files)} files to download") + for file in files : + print(f"Downloading {file['url']}") + base.download_file(file['url'], save_directory + file['name']) # (2)! + ``` + + 1. Ensure that your target directory exists! Directory beginning with `.` are relative to the current working directory + + 2. The download URL is found in the `url` key of each element of the file-type column. The `name` key is used so every files will keep their original names (and you don't have to bother with extensions) + +!!! abstract "Download (detailed two-steps method)" + + This detailed method is for handling complex situations, for example when the file is extremely large or the internet connection is slow. In this example, we assume that a file with URL `https://cloud.seatable.io/dtable-web/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/files/2020-10/invoice.pdf` exists (a file called invoice.pdf and downloaded in October 2020 (`2020-10`) in the base whose uuid is `41cd05da-b29a-4428-bc31-bd66f4600817`, located in the workspace whose id is `74`). + + This method actually relies on two different steps: first getting the file public download link and then downloading it using a `GET` request. - This detailed method is for handling complex situations where the file is extremly large or the internet connection is slow. In this example I assume that there exist a file like `https://cloud.seatable.io/dtable-web/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/files/2020-10/invoice.pdf`. + Compared to the file URL from the `base.download_file` method, the file path needed here is just the "file location" part of the URL corresponding to the location of the file in the base file system (starting **with** `/files/`, `/images/` or `/custom/`). + + !!! info "Download link expires" + + The download link is only valid for some hours. After that the download link must be created again. That's why it's not possible to use permanent download links of files hosted on SeaTable in web pages. For such purpose, we recommend to store the files on public hosting services and to save only the links in SeaTable, which will allow direct use. ``` python - download_link = base.get_file_download_link(file_url) + base.get_file_download_link(file_path) ``` - __Example__ + __Output__ The public download link (looking like `{server_url}/seafhttp/files/{access_token}/{file_name}`). Keep in mind that it's not permanent as the token expires! (throws an error if the file path is wrong) + __Example__ + ``` python + from seatable_api import Base, context import requests + + base = Base(context.api_token, context.server_url) + base.auth() + download_link = base.get_file_download_link('files/2020-10/invoice.pdf') response = requests.get(download_link) + if response.status_code in range(200,300) : # (1)! + with open("invoice.pdf", "wb") as f: # (2)! + f.write(response.content) ``` -!!! question "Download file to local" + 1. `2xx` response status codes correspond to a successful request + + 2. Open the file with write permission and write the response content into it + +!!! abstract "Download file from a custom folder" + + This method is specific for files stored in a custom folder. Compared to the file URL from the `base.download_file` method, the custom path needed here is just the part of the URL corresponding to the location of the file in the custom folders file system (part of the URL starting **after** `/custom/`). In the following example, we consider the file quote.pdf described in the `base.download_file` section uploaded in the custom folder `My Personal Folder` whose URL is `https://cloud.seatable.io/dtable-web/workspace/74/asset-preview/41cd05da-b29a-4428-bc31-bd66f4600817/custom/My Personal Folder/quote.pdf`. + ``` python - base.download_custom_file(path, save_path) + base.download_custom_file(custom_path, save_path) ``` - __Example__ + __Output__ Nothing (throws an error if the URL is invalid or if the save path is wrong) + __Example__ + ``` python - custom_file_path = "/Main/sky.png" - local_path = "/Users/Desktop/sky.png" + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + custom_file_path = "My Personal Folder/quote.pdf" # (1) ! + local_path = "/Users/Desktop/quote.pdf" base.download_custom_file(custom_file_path, local_path) ``` + 1. Unlike the `get_file_download_link` method, `custom_file_part` **doesn't** include `/custom/` + ## Upload -!!! question "Upload (simple method)" +Please note that uploading a file *to a cell* will require two or three steps, depending on the method you use: you'll first need to upload the file to the base, and then you'll be able to update the row with the newly uploaded file details in the cell. You can learn more about this process in the [API Reference](https://api.seatable.com/reference/uploadfile). As for download, there are one simple (one-step) and one detailed (two-steps) process to upload a file: + +!!! abstract "Upload (simple ones-step method)" Upload a file from your local drive, memory or a website. ``` python - base.upload_local_file(file_path, name=None, file_type='file', replace=False) + base.upload_local_file(file_path, name=None, file_type='file', replace=False) # (1)! # or - base.upload_bytes_file(name, content, file_type='file', replace=False) + base.upload_bytes_file(name, content, file_type='file', replace=False) # (2)! ``` - __Example: Upload a file from local hard drive__ + 1. - `name`: the name of the file once uploaded. If `name` is not provided, the uploaded file will keep the same name than the original + + - `file_type`: can be either `file` or `image` (default is `file`) + - `replace`: if set to `True`, uploading a new file with the same name as an existing one will overwrite it (default is `False`) + + 2. When using `base.upload_bytes_file`, `name` is mandatory as their is no named attached to the `content` + + __Output__ File dict containing the same keys as every element in a file-type column: `type` (`file` or `image`), `size`, `name` and `url` + + __Example: Upload a file from local hard drive__ ``` python local_file = '/Users/Markus/Downloads/seatable-logo.png' @@ -82,93 +178,219 @@ import requests file_url = 'https://seatable.io/wp-content/uploads/2021/09/seatable-logo.png' response = requests.get(file_url) - info_dict = base.upload_bytes_file = ('seatable-logo.png', response.content) + if response.status_code in range(200,300) : + info_dict = base.upload_bytes_file = ('seatable-logo.png', response.content) ``` -!!! question "Upload (detailed method)" +!!! abstract "Upload (detailed two-steps method)" - Get a file upload link. + As for the download detailed method, this method actually relies on two different steps: first getting a file upload link and then uploading it using a `POST` request. ``` python base.get_file_upload_link() ``` - __Example__ + __Output__ - `base.get_file_upload_link` outputs a dict containing `upload_link`, `parent_path`, `img_relative_path` and `file_relative_path` keys + - the `POST` request will return a `400` error `Parent dir doesn't exist.` if `parent_dir` is wrong or a `403` error `Access token not found.` if `upload_link` is wrong + + __Example__ + ``` python + import requests + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() # Get the upload link and file path allocated by server upload_link_dict = base.get_file_upload_link() - parent_dir = upload_link_dict['parent_path'] - upload_link = upload_link_dict['upload_link'] + '?ret-json=1' + upload_link = upload_link_dict['upload_link'] # (1)! + parent_dir = upload_link_dict['parent_path'] # (2)! + file_relative_path = upload_link_dict['file_relative_path'] + img_relative_path = upload_link_dict['img_relative_path'] # Upload the file upload_file_name = "file_uploaded.txt" - replace = 1 + replace = True response = requests.post(upload_link, data={ 'parent_dir': parent_dir, - 'replace': 1 if replace else 0 + 'replace': 1 if replace else 0 # (3)! }, files={ - 'file': (upload_file_name, open('/User/Desktop/file.txt', 'rb')) + 'file': (upload_file_name, open('/User/Desktop/file.txt', 'rb')), + 'relative_path': file_relative_path # (4)! }) ``` -!!! question "Upload local file to custom folders" + 1. `upload_link` will look like `{server_url}/seafhttp/upload-api/{temporary_upload_token}` + + 2. `parent_path` will look like `/asset/{base_uuid}`. Please note that the name of the corresponding parameter for the upload `POST` request is `parent_dir`! + + 3. `replace` requires `0` or `1`. You can use this syntax if you prefer to specify `True` or `False` + + 4. Choose either the **file** relative path or the **image** relative path depending on the type of column you want to upload your file to + +!!! abstract "Upload local file to a custom folder" + + This method is specific for files to store in a custom folder. It is the counterpart of the `base.download_custom_file` method. Please note that using this method, existing files will not be replaced (a new `My file(2)` will be created if `My file` already exists). ``` python - base.upload_local_file_to_custom_folder(self, local_path, custom_folder_path=None, name=None) + base.upload_local_file_to_custom_folder(local_path, custom_folder_path=None, name=None) # (1)! ``` - __Example__ + 1. - `custom_folder_path`: the path in the custom folders of the base where you want to upload the file + - `name`: the name of the file once uploaded. If `name` is not provided, the uploaded file will keep the same name than the original + __Output__ Single file dict containing `type`, `size`, `name` and `url` keys. This dict can be used to "assign" a file to a row. + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + + #Step 1: Uploading a file to the base local_path = "/Users/Desktop/sky.png" custom_path = "/Main/" + info_dict = base.upload_local_file_to_custom_folder(local_path, custom_path) - info_dict = base.upload_local_file_to_custom_folder(local_path, custom_path) + #Step 2: Update a row with the uploaded file row_id = "xxxx" - file_col_name = "File" - base.update_row('Table1', row_id, {"File": [info_dict]}) + FILE_COL_NAME = "File" # (1)! + base.update_row('Table1', row_id, {FILE_COL_NAME: [info_dict]}) ``` + 1. Get in the habit of storing column and/or table names in variables, this will make your scripts much easier to update if names change + ## List files -!!! question "List files" +!!! abstract "List files" - List files in custom folders. + List files in any folder of the custom folders file system (use `/` as path if you want to see the content of Custom folders). If you need to list the files present in a system (non-custom) folder, please refer to the [API Reference](https://api.seatable.com/reference/listbaseassets). ``` python - base.list_custom_assets(path) + base.list_custom_assets(path) # (1)! ``` - __Example__ + 1. `path`: **Absolute** path of the directory you want to list the assets for (for example `/My Personal Folder/Photos` for a subdirectory `Photos` located in the directory `My Personal Folder`) + + __Output__ A dict containing a `dir` and a `file` key, each containing a list of respectively directories and files present in the `path` you specified (throws an error if the path is not valid) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() folder_dir = "/Main/photos" - base.list_custom_assets(folder_dir) - # A dict will be returned including dir and file - { - "dir": [{'name': "MyDir"}, ...] - "file":[{'name': "sky.png"}, ....] - } + main_photos_content = base.list_custom_assets(folder_dir) + print(main_photos_content) ``` + __Example: display the whole Custom folders file structure__ + + ``` python + from seatable_api import Base, context + + def list_assets(path): + global indent + if path == "/" : + print(f"πŸ“ {path}") + else : + print(f"{indent}∟ πŸ“ {path.split('/')[-1]}") + assets = base.list_custom_assets(path) + if assets: + indent += ' ' + for f in assets['file']: + print(f"{indent}∟ πŸ“„ {f['name']}") + for d in assets['dir']: # (1)! + if path == '/' : + list_assets(path+d['name']) + else : + list_assets(path+'/'+d['name']) + indent = indent[:-1] + + base = Base(context.api_token, context.server_url) + base.auth() + indent = '' + list_assets('/') # (2)! + ``` + + 1. Recursive function: for each directory of the current directory, the functions calls itself + + 2. The `list_assets` function we created starts at the root level (`/`) + ## Get file info -!!! question "Get file info" +!!! abstract "Get file info" - The data structure returned can be used to updated cells of file column. + This methods allows you to get the file dict of any `name` file in any folder (`path`) of the custom folders file system. ``` python - base.get_custom_file_info(path, name) + base.get_custom_file_info(path, name) # (1)! ``` + 1. `path`: **Absolute** path of the directory you want to list the assets for (for example `/My Personal Folder/Photos` for a subdirectory `Photos` located in the directory `My Personal Folder`) + + __Output__ Single file dict containing `type`, `size`, `name` and `url` keys (throws an error if `path` or `name` is not valid). This dict can be used to "assign" a file to a row. + __Example__ - ``` python - folder_dir = "/Main/" - file_name = "sky.png" - info_dict = base.get_custom_file_info(path, name) - row_id = "xxxx" - file_col_name = "File" - base.update_row('Table1', row_id, {"File": [info_dict]}) - ``` + === "Replace existing content" + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + #Step 1: Get file info + folder_dir = "/Main/" + file_name = "sky.png" + file_dict = base.get_custom_file_info(folder_dir, file_name) + print(file_dict) + #Step 2: Update row content with file info (overwriting current content) + row_id = "fDMHEdraSRuUMNPGEj-4kQ" + FILE_COL_NAME = "File" + base.update_row("Table1", row_id, {FILE_COL_NAME: [file_dict]}) + ``` + + === "Append to content (detailed version)" + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + #Step 1: Get file info + folder_dir = "/Main/" + file_name = "sky.png" + file_dict = base.get_custom_file_info(folder_dir, file_name) + print(file_dict) + #Step 2: Update row content with file info (appending to current content) + row_id = "fDMHEdraSRuUMNPGEj-4kQ" + FILE_COL_NAME = "File" + row = base.get_row("Table1", row_id) + current_files = row[file_col_name] + current_files.append(file_dict) + print(base.update_row("Table1", row_id, {FILE_COL_NAME: current_files})) + ``` + + === "Append to content (short version)" + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + #Step 1: Get file info + folder_dir = "/Main/" + file_name = "sky.png" + file_dict = base.get_custom_file_info(folder_dir, file_name) + print(file_dict) + #Step 2: Update row content with file info (appending to current content) + row_id = "fDMHEdraSRuUMNPGEj-4kQ" + FILE_COL_NAME = "File" + print(base.update_row("Table1", row_id, {FILE_COL_NAME: base.get_row("Table1",row_id)[FILE_COL_NAME] + [file_dict]})) + ``` diff --git a/docs/scripts/python/objects/index.md b/docs/scripts/python/objects/index.md index 6d341fdb..80223585 100644 --- a/docs/scripts/python/objects/index.md +++ b/docs/scripts/python/objects/index.md @@ -1,27 +1,71 @@ -# Predefined Objects and Methods (Python) +# Predefined objects and methods (Python) -Python scripts connects to SeaTable Base with the python library [seatable-api](https://pypi.org/project/seatable-api/). You can find the source code on [GitHub](https://github.com/seatable/seatable-api-python). +This manual list all available objects and methods (also called functions) that are available within Python scripts in SeaTable. When running directly in SeaTable, Python scripts have the ability to access the [base context](context.md). [Date utilities](date-utils.md) are also available. -This manual list all available objects and methods that are availabe within python scripts in SeaTable. +If you compare JavaScript and Python, you will notice that Python has no specific output methods. This is not necessary, because the output is either written into the base or directly returned by the methods. Besides, you'll see that **Python methods never accepts objects** for table, view or row selection arguments, but only their names/`_ids` as strings. Unless otherwise stated, **all method arguments are required**. -!!! Hint "Need specific function?" +## Data model - The Python class `seatable_api` does not yet cover all available functions of the SeaTable API. If you are missing a special function, please contact us at [support@seatable.io](mailto:support@seatable.io) and we will try to add the missing functions. +{% + include-markdown "includes.md" + start="" + end="" +%} -For a more detailed description of the used parameters, refer to the data model at the [SeaTable API Reference](https://api.seatable.com/reference/models). +!!! info "Need a specific function?" -## Authentication + The Python library `seatable_api` does not yet cover all available functions of the SeaTable API. If you are missing a special function, please contact us at [support@seatable.io](mailto:support@seatable.io) and we will try to add the missing functions. -!!! tip "Don't forget to authenticate" +## Getting started - Every python script requires an authorization. All the examples does not contain the required lines of code. +Let's make this concrete and let us look at some basic examples. - ``` +1. Jump to your SeaTable web interface +2. Create a new Python script +3. Copy the following code +4. Run the script + +You will learn from these examples, that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. + + + +=== "1. Add a table to a base" + + This examples shows how to add a table to an existing bases. + + ``` python from seatable_api import Base, context base = Base(context.api_token, context.server_url) - base.auth() + base.auth() # (1)! + + columns=[ + { + "column_type" : "text", + "column_name": "name" + }, + { + "column_type": "number", + "column_name": "age" + } + ] + + base.add_table("ScriptTest", lang='en', columns=columns) ``` -## Base object + 1. These three lines are always required to authorize against the base in SeaTable. + +=== "2. Add a row to this new table" + This examples shows how to add a a record to a table. The example script assumes that a table "ScriptTest" table with two columns "name" and "age" exists in the base. + + ``` python + from seatable_api import Base, context + base = Base(context.api_token, context.server_url) + base.auth() -Base represents a table in SeaTable. The `base` object provide a way to read, manipulate and output data in/from your base. The following methods are available. + row_data = { + 'name': 'Tom', + 'age': 18 + } + + base.append_row('ScriptTest', row_data) + ``` diff --git a/docs/scripts/python/objects/links.md b/docs/scripts/python/objects/links.md index 2c225cd0..36b92512 100644 --- a/docs/scripts/python/objects/links.md +++ b/docs/scripts/python/objects/links.md @@ -1,85 +1,203 @@ -### Links +# Links + +!!! warning "link id and column key" + + `link_id` should not be mistaken with the column `key`! The `key` value is unique (like an id) whereas the link id will be shared between the two linked columns. Please note that `link_id` is used as argument to add/update/remove links, whereas you'll have to provide `link_column_key` (the link column `key`) to get linked records. Both information are available in the column object: + + ```json + { +  "key": "Cp51", /* (1)! */ +  "type": "link", +  "name": "Link column", +  "editable": true, +  "width": 200, +  "resizable": true, +  "draggable": true, +  "data": { +   "display_column_key": "0000", +   "is_internal": true, +   "link_id": "UAmR", /* (2)! */ +   "table_id": "FJkA", /* (3)! */ +   "other_table_id": "nw8k", /* (4)! */ +   "is_multiple": true, +   "is_row_form_view": false, +   "view_id": "", +   "array_type": "text", +   "array_data": null, +   "result_type": "array" +  }, +  "permission_type": "", +  "permitted_users": [], +  "permitted_group": [], +  "edit_metadata_permission_type": "", +  "edit_metadata_permitted_users": [], +  "edit_metadata_permitted_group": [], +  "description": null, +  "colorbys": {} + } + ``` + + 1. The column `key` (referred as `link_column_key` in `base.get_linked_records` arguments) + + 2. The link id of the column (referred as `link_id` in the add/update/remove links operations) + + 3. The table whose id is `table_id` is referred later in this section as the *source* table (the table containing this column) + + 4. The table whose id is `other_table_id` is referred later in this section as the *target* table ## Get link id -!!! question "Get link id" +!!! abstract "get_column_link_id" - Get the link id by column name + Get the link id of the column `column_name` from the table `table_name`. ``` python base.get_column_link_id(table_name, column_name) ``` - __Example__ + __Output__ String (throws an error if no table named `table_name` exists or if no column named `column_name` exists) + __Example__ + ``` python - base.get_column_link_id('Table1', 'Record') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + link_id = base.get_column_link_id('Table1', 'Link column') + print(link_id) ``` ## Get linked records -!!! question "Get linked records" +!!! info "Rows and records, source and target" + + Rows and records are basically the same things. However, to make the following description easier to understand, we will differentiate them: + + - Rows are from the *source* table (the table whose id is `table_id`) + + - Records are the rows from the *target* table (the table linked to the *source* table in the column whose `key` is `link_column_key` or whose link id is `link_id`) + +!!! abstract "get_linked_records" - List the linked records of rows. You can get the linked records of multiple rows. + List the records linked (in the column whose `key` is `link_column_key`) to one or more rows of the *source* table. The row(s) you want to get the linked records from are defined in the `rows` objects (see below). ``` python - base.get_linked_records(table_id, link_column_key, rows) + base.get_linked_records(table_id, link_column_key, rows) # (1)! ``` + 1. `table_id`: the id of *source* table + + `link_column_key`: the column **key** of the link-type column of *source* table (**not** the link id from `base.get_column_link_id`) + + `rows`: a list of dicts, each of them containing: + + - `row_id`: the id of the row we want to get the linked records from + + - `limit`: the maximum number of linked records to get (default is 10) + + - `offset`: the number of first linked records not to retrieve (default is 0) + + __Output__ Single dict where each `key` is the id of a row of the *source* table and the corresponding value is a list of link dicts (see Output structure example below) + __Example__ - ``` python - # rows: a list, each item of the which contains a row info including row_id, offset (defualt by 0) and limit (default by 10) of link table. - base.get_linked_records('0000', '89o4', rows=[ - {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, - {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} - ]) - # a key-value data structure returned as below - # key: row_id of link table - # value: a list which includes the row info of linked table - { - 'FzNqJxVUT8KrRjewBkPp8Q': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ], - 'Jmnrkn6TQdyRg1KmOM4zZg': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ] - } - ``` + === "Function run" + + ```python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + linked_records = base.get_linked_records('0000', '89o4', rows=[ + {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, + {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} + ]) + print(json.dumps(linked_records, indent=' ')) + ``` + + === "Output structure example" + + ```json + { + "FzNqJxVUT8KrRjewBkPp8Q" /* (1)! */: [ + {"row_id": "LocPgVvsRm6bmnzjFDP9bA", "display_value": "1"} /* (2)! */, + {"row_id": "OA6x7CYoRuyc2pT52Znfmw", "display_value": "3"}, + ... + ], + "Jmnrkn6TQdyRg1KmOM4zZg": [ + {"row_id": "LocPgVvsRm6bmnzjFDP9bA", "display_value": "1"}, + {"row_id": "OA6x7CYoRuyc2pT52Znfmw", "display_value": "3"}, + ... + ] + } + ``` + + 1. id of a row of the *source* table + + 2. link object: + + - `row_id` is the id of the linked record (row from the *target* table) + - `display_value` is the displayed in the column whose `key` is `link_column_key` + (from a column of the *target* table) ## Add link -!!! question "Add link" +!!! abstract "add_link" - Add links, link other table records. A link column must already exist. + Add link in a link-type column. You'll need the *source* target's name `table_name`, the *target* table's name `other_table_name`, the link id from the link-type column and both the ids of the rows you want to link: `row_id` for the row from the *source* table and `other_row_id` for the record from the *target* table. ``` python base.add_link(link_id, table_name, other_table_name, row_id, other_row_id) ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no column with ink id `link_id` exists in the *source* table, if no table named `table_name` or `other_table_name` exists or if no row with id `row_id` or `other_row_id` exists in their respective tables) + __Example__ + ```python + base.add_link('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); + ``` + + __Example: Add link to current row__ + ``` python - base.add_link('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') + from seatable_api import Base, context + # Do not hesitate to store the tables' and columns' names at the beginning of your script, + # it will make it really easier to update if names change + TABLE1_NAME = "Table1"; + TABLE1_LINK_COLUMN_NAME = "Table2 link"; + TABLE2_NAME = "Table2"; + + base = Base(context.api_token, context.server_url) + base.auth() + lin_id = base.get_column_link_id(TABLE1_NAME,TABLE1_LINK_COLUMN_NAME); # (1)! + current_row_id = context.current_row['_id']; + base.add_link(lin_id, TABLE1_NAME, TABLE2_NAME, current_row_id, 'J5St2clyTMu_OFf9WD8PbA') ``` -## Update link + 1. Remember you can use `base.get_column_link_id` to get the link id of a specific link-type column. + +## Update link(s) -!!! question "Update link" +!!! abstract "update_link" - Modify the info of link-type column. + Update the content of the link-type column whose link id is `link_id` for the row with id `row_id` in the table `table_name`. It will remove all existing row links and add new links to records of table `other_table_name` with ids in the `other_rows_ids` list. ``` python - update_link(self, link_id, table_name, other_table_name, row_id, other_rows_ids) + base.update_link(link_id, table_name, other_table_name, row_id, other_rows_ids) ``` - __Example__ + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no column with ink id `link_id` exists in the *source* table, if no table named `table_name` or `other_table_name` exists or if no row with id `row_id` exists in the *source* table) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.update_link( link_id='r4IJ', table_name='Table1', @@ -92,19 +210,36 @@ ) ``` -## Batch update links +!!! abstract "batch_update_links" -!!! question "Batch update links" - - Batch update infos of link-type columns. + Same than above,except that it allows you to batch update infos of link-type columns for several rows at once. Learn more about `other_rows_ids_map` in the [SeaTable API Reference](https://api.seatable.com/reference/createrowlink). This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). ``` python - base.batch_update_links(link_id, table_name, other_table_name, row_id_list, other_rows_ids_map) + base.batch_update_links(link_id, table_name, other_table_name, row_id_list, other_rows_ids_map) # (1)! ``` - __Example__ + 1. `row_id_list` is a list containing the ids of all the rows of the source table (whose id is `table_id`) you want to update + + `other_rows_ids_map` is an object with the following syntax, the keys `id_1`,`id_2`,...,`id_n` being **all** the ids of `row_id_list`: + + ``` python + { + 'id_1': [record1['_id'], record2['_id']], + 'id_2': [record5['_id']], + ... + 'id_n': [record1['_id'], recordn['_id']] + } + ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no column with ink id `link_id` exists in the *source* table, if no table named `table_name` or `other_table_name` exists or if no row with one of the id `row_id_list` exists in the *source* table) + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() link_id = "WaW5" table_name = "Table1" other_table_name ="Table2" @@ -118,16 +253,22 @@ ## Remove link -!!! question "Remove link" +!!! abstract "remove_link" - Delete the link row. + Delete the link to the record from table `other_table_name` whose id is `other_row_id` in the row from table `table_name` whose id is `row_id`. ``` python base.remove_link(link_id, table_name, other_table_name, row_id, other_row_id) ``` + __Output__ Dict containing a `success` key with the result of the operation and a `deleted_links_count` with the number of actually deleted links (throws an error if no column with ink id `link_id` exists in the *source* table, if no table named `table_name` or `other_table_name` exists or if no row with id `row_id` or `other_row_id` exists in their respective tables) + __Example__ - + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.remove_link('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') ``` diff --git a/docs/scripts/python/objects/metadata.md b/docs/scripts/python/objects/metadata.md index 74b68383..cb2fbbbf 100644 --- a/docs/scripts/python/objects/metadata.md +++ b/docs/scripts/python/objects/metadata.md @@ -2,64 +2,81 @@ Metadata delivers the complete structure of a base with tables, views and columns. -!!! question "get_metadata" +!!! abstract "get_metadata" Get the complete metadata of a table. The metadata will not contain the concrete rows of the table. - ``` js - base.get_metadata() - ``` + === "Function call" - Example result of this call. + ``` js + base.get_metadata() + ``` - ``` - { - 'tables': [{ - '_id': '4krH', - 'name': 'Contact', - 'is_header_locked': False, - 'columns': [{ - 'key': '0000', - 'type': 'text', - 'name': 'Name', - 'editable': True, - 'width': 200, - 'resizable': True, - 'draggable': True, - 'data': None, - 'permission_type': '', - 'permitted_users': [] - }, { - 'key': 'M31F', - 'type': 'text', - 'name': 'Email', - 'editable': True, - 'width': 200, - 'resizable': True, - 'draggable': True, - 'data': None, - 'permission_type': '', - 'permitted_users': [] - }], - 'views': [{ - '_id': '0000', - 'name': 'Default view', - 'type': 'table', - 'is_locked': False, - 'filter_conjunction': 'And', - 'filters': [], - 'sorts': [], - 'groupbys': [], - 'group_rows': [], - 'groups': [], - 'colorbys': {}, - 'hidden_columns': [], - 'rows': [], - 'formula_rows': {}, - 'link_rows': {}, - 'summaries': {}, - 'colors': {} + === "Output structure" + + ``` + { + 'tables': [{ + '_id': '4krH', + 'name': 'Contact', + 'is_header_locked': False, + 'columns': [{ + 'key': '0000', + 'type': 'text', + 'name': 'Name', + 'editable': True, + 'width': 200, + 'resizable': True, + 'draggable': True, + 'data': None, + 'permission_type': '', + 'permitted_users': [] + }, { + 'key': 'M31F', + 'type': 'text', + 'name': 'Email', + 'editable': True, + 'width': 200, + 'resizable': True, + 'draggable': True, + 'data': None, + 'permission_type': '', + 'permitted_users': [] + }], + 'views': [{ + '_id': '0000', + 'name': 'Default view', + 'type': 'table', + 'is_locked': False, + 'filter_conjunction': 'And', + 'filters': [], + 'sorts': [], + 'groupbys': [], + 'group_rows': [], + 'groups': [], + 'colorbys': {}, + 'hidden_columns': [], + 'rows': [], + 'formula_rows': {}, + 'link_rows': {}, + 'summaries': {}, + 'colors': {} + }] }] - }] - } + } + ``` + + __Example__ + + ```python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + + print(base.get_metadata()) ``` + +!!! info "Displaying long and complex objects" + + If you have hard time reading the output of a function returning a long or complex object, please see [how to make a pretty print](../common_questions.md#printing-complex-elements-is-sometimes-difficult-to-read). diff --git a/docs/scripts/python/objects/notifications.md b/docs/scripts/python/objects/notifications.md deleted file mode 100644 index 1616ef62..00000000 --- a/docs/scripts/python/objects/notifications.md +++ /dev/null @@ -1,22 +0,0 @@ -# Notifications - -## Sent toast notification - -!!! question "Send toast notification" - - Show a toast notification in SeaTable's web interface to a user. - - ```python - base.send_toast_notification(username, msg, toast_type='success') - # toast_type: one of "success", "warning" or "danger" - ``` - - __Example__ - - ```python - base.send_toast_notification( - "aea9e807bcfd4f3481d60294df74f6ee@auth.local", - "error request", - "danger" - ) - ``` diff --git a/docs/scripts/python/objects/rows.md b/docs/scripts/python/objects/rows.md index b43ed1ec..ca01547d 100644 --- a/docs/scripts/python/objects/rows.md +++ b/docs/scripts/python/objects/rows.md @@ -1,205 +1,492 @@ # Rows -## Get row / rows +You'll find below all the available methods to interact with the rows of a SeaTable table. In this section, you'll have to deal with the **id** of the rows. You can find few tips on how to get it in [the user manual](https://seatable.com/help/was-ist-die-zeilen-id/). -!!! question "Get row" +{% + include-markdown "includes.md" + start="" + end="" +%} - Gets one row of a table by its row ID. +## Get row(s) + +!!! abstract "get_row" + + Get a row from table `table_name` via its `row_id`. ``` python base.get_row(table_name, row_id) ``` + __Output__ Single row dict (throws an error if no table named `table_name` exists or if no row with the id `row_id` exists) + __Example__ - + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() row = base.get_row('Table1', 'U_eTV7mDSmSd-K2P535Wzw') ``` -!!! question "List rows" +!!! abstract "list_rows" - Lists multiple rows of a table. + Lists multiple rows of the table `table_name`. If `view_name` is provided, only the rows displayed in this specific view will be returned. The default `limit` is 1000 which is also the maximum number of rows this method returns. The query method (see below) offers more filter options and can return more rows. ``` python - base.list_rows(table_name, view_name=None, start=None, limit=None) + base.list_rows(table_name, view_name=None, start=None, limit=None) # (1)! ``` - The default limit is 1000 which is also the maximum number of rows this method returns. + 1. `view_name` (optional): the name of the view you want to get the rows from. If there is no view named `view_name`, all the rows from table `table_name` will be eventually returned (depending on `start` and `limit`) + + `start` (optional): the index of the first rows you want to get (default is `0`) - The query method (see below) offers more filter options and can return more rows. + `limit` (optional): the maximum number of rows that should be returned (default is 1000, couldn't be higher) - __Examples__ + !!! warning "Mind the indexes!" + In the SeaTable web interface, the row numbers, on the left, start at 1, whereas the `start` argument for the `base.list_rows` method starts at 0! This means that to get as first row the row numbered 10 in the web interface, you'll have to enter `start=9`. + __Output__ List of row dicts (eventually empty if `start` is higher than the number of rows or if the view `view_name` is empty, throws an error if no table named `table_name` exists or if no view named `view_name` exists) + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() rows = base.list_rows('Table1') - rows = base.list_rows('Table1', view_name='default', start=5, limit=20) + rows = base.list_rows('Table1', view_name='Default View', start=5, limit=20) ``` -!!! question "Query" +!!! abstract "query" - Queries a base using a SQL statement. + Use SQL to query a base. SQL queries are the most powerful way access data stored in a base. If your not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). Most SQL syntax is supported, you can check the [SQL Reference](/scripts/sql/introduction.md) section of this manual for more information. ``` python - base.query(sql-statement) + base.query(sql_statement) ``` Unless the SQL statement specifies a higher limit, the method returns a maximum of 100 rows. The maximum number of rows returned is 10000 no matter the limit specified in the SQL statement. + !!! info "Backticks for table or column names containing or special characters or using reserved words" + For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. + + Similarly, if some of your of table or column names are the same as [SQL function](/scripts/sql/functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. + + Similarly, if some of your of table or column names are the same as SQL function names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. + + __Output__ List of row dicts (eventually empty if no row match the request's conditions) + + All the examples below are related to a table **Bill** with the following structure/data: + + | name | price | year | + | ----- | ----- | ----- | + | Bob | 300 | 2021 | + | Bob | 300 | 2019 | + | Tom | 100 | 2019 | + | Tom | 100 | 2020 | + | Tom | 200 | 2021 | + | Jane | 200 | 2020 | + | Jane | 200 | 2021 | + + __Example with a wildcard__ - ``` js - json_data = base.query('select * from Users') // (1)! - print(json.dumps(json_data, indent=2)) - ``` + === "Function call" + + ``` python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + json_data = base.query('select * from Bill') # (1)! + print(json.dumps(json_data, indent=' ')) + ``` + + 1. `*` means that you want to get the whole rows data (columns's values and specific row data such as id, etc.) + + === "Output" - 1. Returns for example the following: ``` json [ { - "Name": "Thomas", - "_id": "VkyADGkFRiif0bEVHd-CtA", - "_ctime": "2023-08-16T15:04:56.018Z", - "_mtime": "2023-08-17T07:02:59.585Z", - "_creator": "a5adebe279e04415a28b2c7e256e9e8d@auth.local", - "_last_modifier": "a5adebe279e04415a28b2c7e256e9e8d@auth.local", + "name": "Bob", + "price": 300, + "year": 2021, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:19.106+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "W77uzH1cSXu2v2UtqA3xSw" + }, + { + "name": "Bob", + "price": 300, + "year": 2019, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:22.112+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "IxONgyDFQxmcDKpZWlQ9XA" + }, + { + "name": "Tom", + "price": 100, + "year": 2019, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-15T10:57:23.4+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "K4LBuQ7aSjK9JwN14ITqvA" + }, + { + "name": "Tom", + "price": 100, + "year": 2020, "_locked": null, "_locked_by": null, - "_archived": false + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "EHcQEaxiRzm3Zvq8B33bwQ" }, { - "Name": "Steve", - "_id": "UevpAVOjRrmbfqMmpsuTEg", - "_ctime": "2023-08-17T07:03:00.292Z", - "_mtime": "2023-08-17T07:03:00.801Z", - "_creator": "a5adebe279e04415a28b2c7e256e9e8d@auth.local", - "_last_modifier": "a5adebe279e04415a28b2c7e256e9e8d@auth.local", + "name": "Tom", + "price": 200, + "year": 2021, "_locked": null, "_locked_by": null, - "_archived": false + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "CjaCdBlNRXKkYkm231shqg" }, + { + "name": "Jane", + "price": 200, + "year": 2020, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "YzmUexIAR7iDWmhKGHgpMw" + }, + { + "name": "Jane", + "price": 200, + "year": 2021, + "_locked": null, + "_locked_by": null, + "_archived": false, + "_creator": "bd26d2b...82ca3fe1178073@auth.local", + "_ctime": "2025-09-18T09:52:00+02:00", + "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", + "_mtime": "2025-09-18T09:52:00+02:00", + "_id": "HJi7wbUMQIOuIlPaoO9Fbg" + } ] ``` __Example with WHERE__ - ``` python - json_data = base.query('select name, price from Bill where year = 2021') - print(json.dumps(json_data, indent=2)) - ``` + === "Function call 1 (filter by year)" - __Example with ORDER BY__ + ```python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + json_data = base.query('select name, price from Bill where year = 2021') + print(json.dumps(json_data, indent=' ')) + ``` + + === "Output #1" + + ```json + [ + {"name":"Bob","price":"300"}, + {"name":"Tom","price":"200"}, + {"name":"Jane","price":"200"} + ] + ``` + + === "Function call 2 (filter by name)" + + ```python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + json_data = base.query('select name, price, year from Bill where name = "Bob"') + print(json.dumps(json_data, indent=' ')) + ``` + + === "Output #2" + + ```json + [ + {"name":"Bob","price":"300","year":"2021"}, + {"name":"Bob","price":"300","year":"2019"} + ] + ``` - ``` python - json_data = base.query('select name, price, year from Bill order by year') - print(json.dumps(json_data, indent=2)) - ``` __Example with GROUP BY__ - ``` python - json_data = base.query('select name, sum(price) from Bill group by name') - print(json.dumps(json_data, indent=2)) - ``` + === "Function call" + + ```python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + json_data = base.query('select name, sum(price) from Bill group by name') + print(json.dumps(json_data, indent=' ')) + ``` + + === "Output" + + ```json + [ + {'name': 'Bob', 'SUM(price)': 600}, + {'name': 'Tom', 'SUM(price)': 400}, + {'name': 'Jane', 'SUM(price)': 400} + ] + ``` __Example with DISTINCT__ - ``` python - json_data = base.query('select distinct name from Bill') - print(json.dumps(json_data, indent=2)) - ``` + === "Function call" + + ```python + import json + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + json_data = base.query('select distinct name from Bill') + print(json.dumps(json_data, indent=' ')) + ``` -## Add rows + === "Output" + + ```json + [ + {'name': 'Bob'}, + {'name': 'Tom'}, + {'name': 'Jane'} + ] + ``` -By default, the default values specified for the table columns in the webinterface do **not** apply when adding/appending rows via API. In order to apply the default values, add `apply_default=True`as a function parameter. If set to True, the default values can be overwritten by specifying alternative values in `row_data`. +## Add row(s) -!!! question "Append row" +!!! info "Dealing with default values" + + By default, the default values specified for the table columns in the web interface do **not** apply when adding/appending rows via Python scripts. In order to apply the default values, add `apply_default=True`as a function parameter. If set to `True`, the default values can be overwritten by specifying alternative values in `row_data`. + +!!! abstract "append_row" + + Add a row to the table `table_name`. This row contains the data specified in the dict `row_data`. No row will be added if `row_data` is an empty dict (`{}`).empty or if it contains only keys that don't exist in the table. - Appends one row to a table. ``` python - base.append_row(table_name, row_data, apply_default=False) + base.append_row(table_name, row_data, apply_default=False) # (1)! ``` - __Example__ + 1. `row_data`: dict (pairs of `key`:`value`, each `key` being the name of a column), for example: + + ``` + { + 'First Name': 'John', + 'Last Name': 'Doe', + 'Invoice amount': 100, + 'Products': ['Office Supplies', 'Computer'] + } + ``` + + `apply_default` (optional): wether to use default values or not (default is `False`) + !!! info "Creating an empty row" + To create an empty row, specify a `row_data` dict containing at least one existing column of the table with an empty string as value, for example: `{'Name': ''}` + + __Output__ Single row dict (`None` if no row were added, throws an error if no table named `table_name` exists) + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() row_data = { "Name": "Ron" } - base.append_row('Table1', row_data, apply_default=True) + row = base.append_row('Table1', row_data, apply_default=True) + print(row) ``` -!!! question "Batch append rows" +!!! abstract "batch_append_rows" - Appends multiple rows to a table. + Append multiple rows to the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). ``` python - base.batch_append_rows(table_name, rows_data, apply_default=False) + base.batch_append_rows(table_name, rows_data, apply_default=False) # (1)! ``` + 1. `rows_data`: list of `row_data` dict (see `base.append_row` above) + + `apply_default` (optional): wether to use default values or not (default is `False`) + + __Output__ Single dict object containing the number of new rows, the list of the ids of the created rows and the first row (see example output below); throws an error if no table named `table_name` exists + __Example__ + + === "Function call" + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + rows_data = [{ + 'Name': 'Ron', + 'Birthday': '1975-01-01' + }, { + 'Name': 'Richard', + 'Birthday': '1978-10-08' + }] + + rows = base.batch_append_rows('Table1', rows_data) + print(rows) + ``` - ``` python - rows_data = [{ - 'Name': 'Ron', - 'Birthday': '1975-01-01' - }, { - 'Name': 'Richard', - 'Birthday': '1978-10-08' - }] + === "Output" + + ```json + { +   "inserted_row_count": 2, /* (1)! */ +   "row_ids": [ /* (2)! */ +   { +     "_id": "bglW5pKfQxG9D70hc693Wg" +   }, +   { +     "_id": "Q3E3IJWrTQCjOOxjipM8jA" +   } + ], +   "first_row": { /* (3)! */ +   "0000": "Ron", +   "1JGG": "1975-01-01", +   "_creator": "cc7a1d0fcec84bf9b36df5dcf5b65b99@auth.local", +   "_last_modifier": "cc7a1d0fcec84bf9b36df5dcf5b65b99@auth.local", +   "_id": "bglW5pKfQxG9D70hc693Wg", +   "_ctime": "2025-09-24T14:52:55.651+00:00", +   "_mtime": "2025-09-24T14:52:55.651+00:00" + } + } + ``` - base.batch_append_rows('Table6', rows_data) - ``` + 1. `inserted_row_count`: number of new rows + + 2. `row_ids`: list of dicts, each containing a single `_id` key and the id of the corresponding created row as value -!!! question "Insert row" + 3. `first_row`: the row dict of the first created row - Inserts one row to a table under a anchor row. +!!! abstract "insert_row" + + Insert one row to the table `table_name` under an *anchor* row whose id is `anchor_row_id`. If no row with id `anchor_row_id` exists, the row is added to the end of the table (similar to `base.append_row` in ths case). ``` python base.insert_row(table_name, row_data, anchor_row_id, apply_default=False) ``` - __Example__ + __Output__ Single row dict (`None` if no row were added, throws an error if no table named `table_name` exists) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() row_data = { "Name": "Ron" } - base.insert_row('Table1', row_data, 'U_eTV7mDSmSd-K2P535Wzw') + row = base.insert_row('Table1', row_data, 'U_eTV7mDSmSd-K2P535Wzw') + print(row) ``` -## Update row +## Update row(s) -!!! question "Update row" +!!! abstract "update_row" - Updates one row in a table. + Update the row whose id is `row_id` in the table `table_name`. The `row_data` dict (pairs of `key`:`value`, each `key` being the name of a column) need to contain only the data you want to update. To reset a value, specify the `key`:`value` pair with an empty string `''`. ``` python base.update_row(table_name, row_id, row_data) ``` - __Example__ + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists or if no row with the id `row_id` exists) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() row_data = { "Name": "Ron" } - base.update_row('Table1', 'U_eTV7mDSmSd-K2P535Wzw', row_data) + row_update = base.update_row('Table1', 'U_eTV7mDSmSd-K2P535Wzw', row_data) + print(row_update) ``` -!!! question "Batch update rows" +!!! abstract "batch_update_rows" - Updates multiple rows in a table. + Updates multiple rows in the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). ``` python - base.batch_update_rows(table_name, rows_data) + base.batch_update_rows(table_name, rows_data) # (1)! ``` - __Example__ + 1. `rows_data`: list of dicts containing two `key`:`value` pairs: + + - `row_id`: the id of the row to update + - `row`: the dict containing the row data to update (see `base.append_row` above) + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists, if no row with the id `row_id` exists or if `rows_data` is wrong, for example with non-existing `row_id` value) + + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() rows_data = [{ "row_id" : "fMmCFyoxT4GN5Y2Powbl0Q", "row" : { @@ -216,41 +503,61 @@ By default, the default values specified for the table columns in the webinterfa "row_id" : "WP-8rb5PSUaM-tZRmTOCPA", "row" : { "Name" : "Regina", - "Heigt" : "173" + "Height" : "173" } }] - base.batch_update_rows('Table1', rows_data) + row_update = base.batch_update_rows('Table1', rows_data) + print(row_update) ``` -## Delete rows +## Delete row(s) -!!! question "Delete row" +!!! abstract "delete_row" - Deletes one row from a table. + Delete a single row (whose id is `row_id`) from the table `table_name`. ``` python base.delete_row(table_name, row_id) ``` - __Example__ + __Output__ Dict containing a single `deleted_rows` key with the number of deleted rows (`0` if no row with id `row_id` exists, throws an error if no table named `table_name` exists) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() base.delete_row('Table1', 'U_eTV7mDSmSd-K2P535Wzw') ``` -!!! question "Batch delete rows" +!!! abstract "batch_delete_rows" - Deletes multiple rows from a table. + Delete multiple rows from the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). ``` python - base.batch_delete_rows(table_name, row_ids) + base.batch_delete_rows(table_name, row_ids) # (1)! ``` - __Example__ + 1. `row_ids`: list of the ids of the rows to delete + + __Output__ Dict containing a single `deleted_rows` key with the number of deleted rows (`0` if `row_ids` is an empty list, throws an error if no table named `table_name` exists) + __Example__ + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + # Retrieving the rows of table 'Table1' + rows = base.list_rows('Table1') + #Getting only the three first rows del_rows = rows[:3] + # Creating a list of the ids from these three rows row_ids = [row['_id'] for row in del_rows] - base.batch_delete_rows('Table1', row_ids) + deletion_result = base.batch_delete_rows('Table1', row_ids) + print(deletion_result) ``` diff --git a/docs/scripts/python/objects/tables.md b/docs/scripts/python/objects/tables.md index 4f2e6cd5..e25cadc6 100644 --- a/docs/scripts/python/objects/tables.md +++ b/docs/scripts/python/objects/tables.md @@ -1,66 +1,148 @@ -# Table +# Tables -!!! question "List tables" +You'll find below all the available methods to interact with the tables of a SeaTable base. - Get the active table. +{% + include-markdown "includes.md" + start="" + end="" +%} + +You can have a look at the specific [view](./views.md#global-structure), [column](./columns.md#global-structure) or [row](./rows.md#global-structure) structure on the corresponding pages. + +## Retrieve table(s) + +!!! info "Get current table" + + There is no specific method to get the current (selected) table as it is a property from the [context object](./context.md), + so simply use `context.current_table`. + +!!! abstract "list_tables" + + Get all tables of the current base. ``` python base.list_tables() ``` + __ Output__ List of table dicts __Example__ - ``` js + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() tables = base.list_tables() + print(tables) ``` -!!! question "Get a table by name" +!!! abstract "get_table_by_name" - Get the active table. + Get a table object by its name. ``` python base.get_table_by_name(table_name) ``` - + __Output__ Single table dict (`None` if there is no table named `table_name`) + __Example__ - ``` js - base.get_table_by_name('Table1') + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + table = base.get_table_by_name('Table1') + print(table) ``` -!!! question "Add table" +## Add table + +!!! abstract "add_table" - Add a table into a base. + Add a table named `table_name` into a base. The `columns` argument is an optional list of [column objects](./columns.md#global-structure). ``` python - base.add_table(table_name, lang='en', columns=[]); + base.add_table(table_name, lang='en', columns=[]) # (1)! ``` + 1. `lang` (optional): can be `en` (default) for English or `zh-cn` for Chinese and will determine the name of the first `Name` column (if no `columns` where specified) + `columns` (optional): list of [column objects](./columns.md#global-structure) describing the columns of the new table. + + __Output__ Single table dict (throws an error if a table named `table_name` already exists) + __Example__ - ``` js - base.add_table('Investigation', lang='zh-cn') + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + new_table = base.add_table('Investigation', lang='zh-cn') + print(new_table) ``` -!!! question "Rename table" + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + + columns=[ + { + "column_type" : "text", + "column_name": "name" + }, + { + "column_type": "number", + "column_name": "age" + } + ] + + base.add_table("ScriptTest", lang='en', columns=columns) + ``` + +## Rename table + +!!! abstract "rename_table" - Add a table into a base. + Rename an existing table named `table_name` to `new_table_name`. ``` python base.rename_table(table_name, new_table_name) ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists) __Example__ - ``` js - base.rename_table('Table1', 'Table11') - ``` + === "Function call" + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + print(base.rename_table('Table1', 'Table11')) + ``` + === "Output" + ```python + {'success': True} + ``` + +## Delete table -!!! question "Delete table" +!!! abstract "delete_table" - Add a table into a base. + Delete a table named `tableName` from the base. By the way, the table can be [restored from the logs](https://seatable.com/help/eine-geloeschte-tabelle-wiederherstellen/). Deleting the last table is not possible. ``` python base.delete_table(table_name) ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists or if you try to delete the last table) + __Example__ - ``` js - base.delete_table('Table1') + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + delete_table_success = print(base.delete_table('Table1')) + print(delete_table_success) ``` diff --git a/docs/scripts/python/objects/users.md b/docs/scripts/python/objects/users.md index 3da4b9bc..5753b226 100644 --- a/docs/scripts/python/objects/users.md +++ b/docs/scripts/python/objects/users.md @@ -1,17 +1,25 @@ -# User +# Users -## get_user_info +## Get user info -!!! question "Get a user info" +!!! abstract "get_user_info" - Returns the name of the user and his `id_in_org`. + Returns the name of the user and his ID (the one you can see in your [profile](https://seatable.com/help/persoenliche-einstellungen/)). The username you have to provide is a unique identifier ending by `@auth.local`. This is **neither** the email address of the user **nor** its name. ``` python base.get_user_info(username) ``` + __Output__ Dict containing `id_in_org` and `name` keys + + __Example__ ``` python - base.get_user_info("aea9e807bcfd4f3481d60294df74f6ee@auth.local") + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + user_info = base.get_user_info("aea9e807bcfd4f3481d60294df74f6ee@auth.local") + print(user_info) ``` diff --git a/docs/scripts/python/objects/views.md b/docs/scripts/python/objects/views.md index e1aa1699..8b2e0f32 100644 --- a/docs/scripts/python/objects/views.md +++ b/docs/scripts/python/objects/views.md @@ -1,75 +1,120 @@ # Views -Every table in a base contains views. The following calls are available to interact with the views of a table. +You'll find below all the available methods to interact with the views of a SeaTable table. -## List views +{% + include-markdown "includes.md" + start="" + end="" +%} -!!! question "List views" +## Get view(s) +!!! abstract "get_view_by_name" + + Get a view of the table `table_name`, specified by its name `view_name`. ``` python - base.list_views(table_name) + base.get_view_by_name(table_name, view_name) ``` + __Output__ Single view dict (throws an error if no view called `view_name` exists or if no table named `table_name` exists) __Example__ - + ``` python - base.list_views('Table1') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + view = base.get_view_by_name('Table1', 'Default View') + print(view) ``` -!!! question "Get view by name" +!!! abstract "list_views" + + Get all the views of the table named `table_name`. ``` python - base.get_view_by_name(table_name, view_name) + base.list_views(table_name) ``` - __Example__ + __Output__ Dict with a single `views` key containing a list of the table's views (throws an error if no table named `table_name` exists) + __Example__ + ``` python - base.get_view_by_name('Table1', 'MyView') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + views = base.list_views('Table1') + print(views) ``` ## Add view -!!! question "Add view" +!!! abstract "add_view" + Add a view named `view_name` to the table `table_name`. ``` python base.add_view(table_name, view_name) ``` - __Examples__ + __Output__ Single view dict (throws an error if a view called `view_name` already exists or if no table named `table_name` exists) + + __Example__ ``` python - base.add_view('Table1', 'New view') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + view = base.add_view('Table1', 'New view') + print(view) ``` ## Rename view -!!! question "Rename view" +!!! abstract "rename_view" + Rename a view in the table `table_name` specified by its current name `view_name` and its new name `new_view_name`. Please ensure that no view named `new_view_name` already exists in the table `table_name`. ``` python base.rename_view(table_name, view_name, new_view_name) ``` + __Output__ Single view dict (throws an error if no view called `view_name` exists or if no table named `table_name` exists) + __Example__ ``` python - base.rename_view('Table1', 'MyView', 'NewView') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + view = base.rename_view('Table1', 'MyView', 'NewView') + print(view) ``` ## Delete view -!!! question "Delete view" +!!! abstract "delete_view" + Delete a view in the table `table_name`, specified by its name `view_name`. **DO NOT** try to delete the last view or you might no longer be able to access your table! ``` python base.delete_view(table_name, view_name) ``` + __Output__ Dict containing a single `success` key with the result of the operation (throws an error if no table named `table_name` exists). Be careful, `{'success':True}` will be returned even if no view named `view_name` exists! + __Example__ ``` python - base.delete_view('Table1', 'MyView') + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + print(base.delete_view('Table1', 'MyView')) ``` diff --git a/docs/scripts/python/objects/websockets.md b/docs/scripts/python/objects/websockets.md deleted file mode 100644 index 15c1978a..00000000 --- a/docs/scripts/python/objects/websockets.md +++ /dev/null @@ -1,55 +0,0 @@ -# Websockets - -## socketIO - -!!! question "socketIO" - - By using websocket, you can get __realtime data update notifications__ of a base. - - ```python - from seatable_api import Base - - server_url = 'https://cloud.seatable.cn' - api_token = 'c3c75dca2c369849455a39f4436147639cf02b2d' - - base = Base(api_token, server_url) - base.auth(with_socket_io=True) - - base.socketIO.wait() - ``` - - When Base has data updated, the following will be output in the terminal. - - ```log - 2022-07-19 11:48:37.803956 [ SeaTable SocketIO connection established ] - 2022-07-19 11:48:39.953150 [ SeaTable SocketIO on UPDATE_DTABLE ] - {"op_type":"insert_row","table_id":"0000","row_id":"YFK9bD1XReSuQ7WP1YYjMA","row_insert_position":"insert_below","row_data":{"_id":"RngJuRa0SMGXyiA-SHDiAw","_participants":[],"_creator":"seatable@seatable.com","_ctime":"","_last_modifier":"seatable@seatable.com","_mtime":""},"links_data":{}} - ``` - - After getting data update notifications, performance self-defined actions by listen to the UPDATE_DTABLE event. - - ```python - import json - from seatable_api import Base - from seatable_api.constants import UPDATE_DTABLE - - server_url = 'https://cloud.seatable.cn' - api_token = 'c3c75dca2c369849455a39f4436147639cf02b2d' - - def on_update(data, index, *args): - try: - operation = json.loads(data) - print(operation) - op_type = operation['op_type'] - table_id = operation['table_id'] - row_id = operation['row_id'] - # ... do something - except Exception as e: - print(e) - - base = Base(api_token, server_url) - base.auth(with_socket_io=True) - - base.socketIO.on(UPDATE_DTABLE, on_update) - base.socketIO.wait() - ``` diff --git a/docs/scripts/sql/functions.md b/docs/scripts/sql/functions.md index d4ef71e1..2ddbab92 100644 --- a/docs/scripts/sql/functions.md +++ b/docs/scripts/sql/functions.md @@ -1,183 +1,866 @@ # SQL function reference -You can use supported functions in SQL query statements. +With functions you can transform, calculate, combine or merge the values of other columns from the current table. On top of that, functions can refer to each other. In this article, we will show you a complete overview of all functions with examples. If you are looking for a specific function, you can use Ctrl+F or ⌘+F to quickly find an entry on this page. -## Functions for SQL +The functions supported in SQL are roughly the same as the set of functions supported by formulas in SeaTable. The function parameters can be numbers, strings, constants, column names or other functions. Column name cannot be an alias. The function can be classified into the following categories: -With functions you can transform, calculate, combine or merge the values of other columns from the current table. On top of that, functions can refer to each other. +- [Operators](#operators) +- [Mathematical functions](#mathematical-functions) +- [Text functions](#text-functions) +- [Date functions](#date-functions) +- [Geo functions](#geo-functions) +- [Logical functions](#logical-functions) +- [Statistical functions](#statistical-functions) -The functions supported in SQL are roughly the same as the set of functions supported by formulas in SeaTable. +!!! info "Backticks for table or column names containing or special characters or using reserved words" + For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` select * from `My Table` ``. -The basic syntax of functions is as follows: + Similarly, if some of your of table or column names are the same as SQL function names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. -``` -FunctionName(parameters...) -``` +## Constants -The parameters can be number,string,constants,column name or other functions. Column name cannot be an alias. If the column name contains "-", you can use "`" to enclose it. +You can use the following constants in the functions: -Currently SQL query offers the following functions: +| VARIABLE | DESCRIPTION | +| :------- | :-------------------------------------- | +| `e` | Returns the Euler number e=2.71828... | +| `pi` | Returns the circle number Ο€=3.14159... | +| `true` | Returns the logical value `true`. | +| `false` | Returns the logical value `false`. | -- Operands -- Mathematical functions -- Text functions -- Date functions -- Geo functions -- Logical functions -- Statistical functions +## Operators -In this article, we will show you a complete overview of all functions with examples. If you are looking for a specific function, you can use the Ctrl+F to quickly find an entry on this page. +Parameters must be strings or numbers. If a number is passed to a parameter that expects a string, it'll be converted to string, and vice versa. -## Functions with examples +### Arithmetic operators -You can use the following constants in the function: +!!! abstract "add" + Adds two numeric values (`num1` and `num2`) and returns the result. -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :------- | :------------------------------------ | :------ | :--------- | -| e | Returns the Euler number e=2.71828... | e+1 | 3.71828183 | -| pi | Returns the circle number Pi. | pi | 3.14159265 | -| true() | Returns the logical value 'true'. | true() | true | -| false() | Returns the logical value 'false'. | false() | false | + ``` + add(num1,num2) + ``` -### Operands + __Example__ `add(1,2)` returns `3` -Parameters must be strings or numbers. If a number is passed to a parameter that expects a string, it'll be converted to string, and vice versa. +!!! abstract "substract" + Subtracts one numeric value (`num2`) from another (`num1`). + + ``` + substract(num1,num2) + ``` + + __Example__ `substract(5,4)` returns `1` + +!!! abstract "multiply" + Multiplies two numeric values. + + ``` + multiply(num1,num2) + ``` + + __Example__ `multiply(3,4)` returns `12` + +!!! abstract "divide" + Divides one numeric value (`num1`) by another (`num2`). + + ``` + divide(num1,num2) + ``` + + __Example__ `divide(3,2)` returns `1.5` + +!!! abstract "mod" + Calculates the remainder of a division. + + ``` + mod(num1,num2) + ``` + + __Example__ `divide(15,7)` returns `1` + +!!! abstract "power" + Calculates the power (`num2`) of a number (`num1`). + + ``` + power(num1,num2) + ``` + + __Example__ `power(3,2)` returns `9` + +### Greater-Less comparisons + +!!! abstract "greater" + Checks if a numeric value (`num1`) is greater than another (`num2`) and returns the logical value `true` or `false`. + + ``` + greater(num1,num2) + ``` + + __Example__ `greater(2,3)` returns `false` + +!!! abstract "lessthan" + Checks if a numeric value (`num1`) is less than another (`num2`) and returns the logical value `true` or `false`. + + ``` + lessthan(num1,num2) + ``` + + __Example__ `lessthan(2,3)` returns `true` + +!!! abstract "greatereq" + Checks if a numeric value (`num1`) is greater than or equal to another (`num2`) and returns the logical value `true` or `false`. + + ``` + greatereq(num1,num2) + ``` + + __Example__ `greatereq(2,2)` returns `true` + +!!! abstract "lessthaneq" + Checks if a numeric value (`num1`) is less than or equal to another (`num2`) and returns the logical value `true` or `false`. + + ``` + lessthaneq(num1,num2) + ``` + + __Example__ `lessthaneq(2,2)` returns `true` + + +### Equal-Not equal comparisons + +The functions work for both numbers and strings. + +!!! abstract "equal" + Checks if two values (`num1`, `num2`) are equal and returns the logical value `true` or `false`. + + ``` + equal(num1,num2) + ``` + + __Example__ ```equal(`Old price`,`New price`)``` compares the content of the `Old price` and the `New price` columns and returns `true` or `false` accordingly + +!!! abstract "unequal" + Checks if two values (`num1`, `num2`) are not equal and returns the logical value `true` or `false`. + + ``` + unequal(num1,num2) + ``` + + __Example__ ```unequal(`Single select`,"Option 1")``` compares the content of the `Single select` column to the string "Option 1" and returns `true` or `false` accordingly + +## Mathematical functions + +Parameters must be numbers. If a string is passed as parameter, it will be converted to number. + +!!! abstract "abs" + Returns the absolute value of a `number`. + + ``` + abs(number) + ``` + + __Example__ `abs(-2)` returns `2` + +!!! abstract "ceiling" + Rounds a `number` to the nearest greater integer or to the nearest greater multiple of the specified `significance`. If either argument is non-numeric, the formula returns an empty value. + + ``` + ceiling(number, significance) + ``` + + __Example__ `ceiling(2.14)` returns `3` + + If the `number` is an exact multiple of the `significance`, then no rounding occurs. If the `number` and the `significance` are negative, then the rounding is away from 0. If the `number` is negative and the `significance` is positive, then the rounding is towards 0. + + + __Example__ `ceiling(-2.14, 4)` returns `0` + +!!! abstract "even" + Returns the nearest greater even `number`. + + ``` + even(number) + ``` + + __Example__ `even(2.14)` returns `4` + +!!! abstract "exp" + Exponential function for Euler's `number` e. Returns the value of e to the power of `number`. + + ``` + exp(number) + ``` + + __Example__ `exp(1)` returns `2.71828...` + +!!! abstract "floor" + Rounds a `number` to the nearest smaller integer or to the nearest smaller multiple of the specified `significance`. If either argument is non-numeric, the formula returns an empty value. + + ``` + floor(number, significance) + ``` + + __Example__ `floor(2.86)` returns `2` + + If the `number` is an exact multiple of the `significance`, then no rounding takes place. If the sign of the `number` is positive, then the rounding is towards 0. If the sign of the `number` is negative, then the rounding is away from 0. + + + __Example__ `floor(-3.14, 5)` returns `-5` + +!!! abstract "int" + Assigns the nearest smaller integer to a real `number`. + + ``` + int(number) + ``` + + __Example__ `int(-3.14)` returns `-4` + +!!! abstract "lg" + Logarithm function with 10 as base. + + ``` + lg(number) + ``` + + __Example__ `lg(100)` returns `2` + +!!! abstract "log" + Logarithm function with a definable `base`. + + ``` + log(number, base) + ``` + + __Example__ `log(81, 3)` returns `4` + + But if no `base` is given, this function works exactly like lg(), with 10 as `base`. + + + __Example__ `log(1000)` returns `3` + +!!! abstract "odd" + Returns the nearest greater odd `number`. + + ``` + odd(number) + ``` + + __Example__ `odd(-2.14)` returns `-1` + +!!! abstract "round" + Rounds a `number` to the nearest integer. If no decimal place (`digits`) is specified, the `number` is rounded to an integer. + + ``` + round(number, digits) + ``` + + __Example__ `round(3.14)` returns `3` + + If a positive decimal place (`digits`) is given, the result will have `digits` decimals. + + + __Example__ `round(3.14, 1)` returns `3.1` + + If a negative decimal place (`digits`) is given, the result is rounded to the left of the decimal point. + + + __Example__ `round(3.14, -3)` returns `0` + +!!! abstract "rounddown" + Rounds a `number` towards zero. If no decimal place (`digits`) is given, the `number` is rounded to an integer. + + ``` + rounddown(number, digits) + ``` + + __Example__ `rounddown(3.12, 1)` returns `3.1` + +!!! abstract "roundup" + Rounds a `number` away from zero. If no decimal place (`digits`) is given, the `number` is rounded to an integer. + + ``` + roundup(number, digits) + ``` + + __Example__ `roundup(-3.15)` returns `-4` + +!!! abstract "sign" + Checks whether a `number` is greater, equal or less than 0. Returns the values 1, 0 and -1 respectively. In other words: it returns the sign of a `number`, for '+', 'zero' and '-' with 1, 0, and -1 respectively. + + ``` + sign(number) + ``` + + __Example__ `sign(-2)` returns `-1` + +!!! abstract "sqrt" + Returns the square root of a `number`. + + ``` + sqrt(number) + ``` + + __Example__ `sqrt(81)` returns `9` + +## Text functions + +!!! abstract "concatenate" + Combines several strings (`string1`, `string 2`, ...) into one single string. + + ``` + concatenate(string1, string2, ...) + ``` + + __Example__ `concatenate(`Supplier`, " has the product ", `Product`)` returns for example `Microsoft has the product GitHub` if `Supplier` column contains "Microsoft" and `Product` column contains "GitHub" + +!!! abstract "exact (Aβ‰ a case sensitive)" + Checks whether two strings (`string1`, `string2`) are exactly identical. Returns the values `true` or `false` respectively. + + ``` + exact(string1, string2) + ``` + + __Example__ `exact('SeaTable', 'Seatable')` returns `false` + +!!! abstract "find (Aβ‰ a case sensitive)" + Returns the start position of a string (`findString`) within another string (`sourceString`). The numbering starts at 1. If not found, 0 is returned. If the start position (`startPosition`) is given as decimal, it is rounded down. If the cell in the column for the keyword (`findString`) is empty, 1 is returned. If the cell in the column for the target string (`sourceString`) is empty, an empty value ('') is returned. + + ``` + find(findString, sourceString, startPosition) + ``` + + __Example__ `find('Sea', 'seaTable', 1)` returns `0` + + The search will start from the given `startPosition`. This startPosition` has no influence on the result: it always returns the absolute start position. If the '`startPosition`' of the string to be searched for (`findString`) is given after the actual start position of the string (`sourceString`), 0 is returned, since nothing was found from this position. + + + __Example__ `find('table', 'big table', 4)` returns `5` + +!!! abstract "left" + Returns the specified number (`count`) of characters at the beginning of a `string`. + + ``` + left(string, count) + ``` + + __Example__ `left('SeaTable', 3)` returns `Sea` + +!!! abstract "len" + Returns the number of characters in a `string`. + + ``` + len(string) + ``` + + __Example__ `len('SeaTable')` returns `8` + +!!! abstract "lower" + Converts a character `string` to lower case letters. + + ``` + lower(string) + ``` + + __Example__ `lower('German)` returns `german` + +!!! abstract "mid" + Returns the specified number (`count`) of characters from the specified start position (`startPosition`) of a `string`. + + ``` + mid(string, startPosition, count) + ``` + + __Example__ `mid('SeaTable is the best', 1, 8)` returns `SeaTable` + + Start position (`startPosition`) and `count` must not be empty, negative or zero. However, if start position (`startPosition`) and number (`count`) are given as decimal, they are rounded down. Too much `count` is ignored. + + + __Example__ `mid('SeaTable is the best.', 10.9, 27.3)` returns `is the best.` + +!!! abstract "replace" + Replaces a part (`count`) of a character string (`sourceString`) from a certain start position (`startPosition`) with another character string (`newString`). The number (`count`) of characters is only taken into account for the old string (`sourceString`), but not for the new string (`newString`). + + ``` + replace(sourceString, startPosition, count, newString) + ``` + + __Example__ `replace('SeaTable is the best.', 1, 8, 'Seafile')` returns `Seafile is the best.` + + If number (`count`) is given as zero, the new string (`newString`) is simply added to the old string (`sourceString`) from the start position (`startPosition`). + + + __Example__ `replace('SeaTable is the best.', 1, 0, 'Seafile')` returns `SeafileSeaTable is the best.` + +!!! abstract "rept" + Repeats a `string` as often (`number`) as specified. + + ``` + rept(string, number) + ``` + + __Example__ `rept('Sea ', 3)` returns `SeaSeaSea` + +!!! abstract "right" + Returns the specified number (`count`) of characters at the end of a `string`. + + ``` + right(string, count) + ``` + + __Example__ `right('SeaTable', 5)` returns `Table` + +!!! abstract "search ((A=a NOT case sensitive))" + Returns the start position of a string (`findString`) within another string (`sourceString`). The numbering starts at 1. If not found, 0 is returned. If the start position (`startPosition`) is given as decimal, it is rounded down. If the cell in the column for the keyword (`findString`) is empty, 1 is returned. If the cell in the column for the target string (`sourceString`) is empty, an empty value ('') is returned. + + ``` + search(findString, sourceString, startPosition) + ``` + + __Example__ `search('Sea', 'seaTable', 1)` returns `1` + + The search will start from the given `startPosition`. This `startPosition` has no influence on the result: it always returns the absolute start position. If the `startPosition` of the character string to be searched for (`findString`) is given after the actual start position of the character string (`sourceString`), 0 is returned, since nothing was found from this position. + + + __Example__ `search('table', 'big table', 6)` returns `0` + +!!! abstract "substitute (Aβ‰ a case sensitive)" + Replaces existing text (`oldString`) with new text (`newString`) in a string (`sourceString`). If there is more than one text (`oldString`) in the string (`sourceString`), only the `index`-th text is replaced. + + ``` + substitute(sourceString, oldString, newString, index) + ``` + + __Example__ `substitute('SeaTableTable', 'Table', 'file', 1)` returns `SeafileTable` + + If the `index` is given as 0 or not, all found text (`oldString`) will be replaced by the new text (`newString`). + + + __Example__ `substitute('SeaTableTable', 'Table', 'file')` returns `Seafilefile` + +!!! abstract "T" + Checks whether a `value` is text. If so, the text is returned. If no, the return `value` is empty. + + ``` + T(value) + ``` + + __Example__ `T('123')` returns `123` + +!!! abstract "text" + Converts a `number` into text and `format`s it in the specified `format`. The `format` can be percent, number, dollar, euro or yuan. + + ``` + text(number, format) + ``` + + __Example__ `text(150, 'euro')` returns `€150` + + When a `number` is converted directly to percent, its absolute value is retained. In other words, 50 is converted into 5000%. But if you want 50%, you have to divide the `number` by 100 before the conversion. + + + __Example__ `text(50, 'percent')` returns `5000%` + +!!! abstract "trim" + Removes spaces at both the beginning and the end of a `string`. + + ``` + trim(string) + ``` + + __Example__ `trim(' SeaTable ')` returns `SeaTable` + +!!! abstract "upper" + Converts a `string` to uppercase letters. + + ``` + upper(string) + ``` + + __Example__ `upper('German)` returns `GERMAN` + +!!! abstract "value" + Converts a text (`string`) representing a number into a number. + + ``` + value(string) + ``` + + __Example__ `value('123')` returns `123` + + +## Date functions + +When passing a parameter with time or date type, you can specify a constant in "2025-09-01 12:00:01" or "2025-09-01" format. When you query the result of a date function in SQL, the result will be converted to a string in RFC3339 format, e.g. "2025-09-03T00:00:00+02:00". Please note that if a date function returns a date, it cannot be used as parameter for text or maths functions. + +!!! abstract "date" + Returns a date in international format (ISO) from entered `year`, `month` and `day`. If the `year` is entered with two digits, it is automatically understood as a year in the 1900s. If the number of the `month` or `day` is too large (greater than 12 or 31 respectively), these months or days are automatically converted to the next year or month. + + ``` + date(year, month, day) + ``` + + __Example__ `date(2025, 1, 3)` returns `2025-01-03T00:00:00+02:00` + +!!! abstract "dateAdd" + Adds the specified number (`count`) of years ('years'), months ('months'), weeks ('weeks'), days ('days'), hours ('hours'), minutes ('minutes') or seconds ('seconds') to a datetime (`date`). + + ``` + dateAdd(date, count, unit) + ``` + + __Example__ `dateAdd('2024-02-03', 2, 'days')` returns `2024-02-05T00:00:00+02:00` + + Tip: if you want to add a complex duration (`count`) such as 1 day 12 hours, you can convert it to e.g. 24+12=36 hours ('hours') and enter it into the formula as a uniform duration (`count`). The duration is converted to the smallest `unit`: in this case, hours. + + + __Example__ `dateAdd('2024-09-04 13:05:18', 36, 'hours') OR dateAdd(`form submission`, 36, 'hours')` returns `2024-09-06T01:05:18+02:00` + +!!! abstract "dateDif" + Calculates the seconds, days, months, or years between two date values. The optional `unit` argument can be one of the following: S (seconds), D (full days), M (full months), Y (full years), YD (full days, ignoring years), YM (full months, ignoring days and years), MD (full days, ignoring months and years). If the `startDate` is empty, a default value of "1900-01-01" will be set. If both date values are empty, it will return 0. + + ``` + dateDif(startDate, endDate, unit) + ``` + + __Example__ `dateDif('2023-01-01', '2025-01-01','Y')` returns `2` + + + __Example__ `dateDif('2024-10-11', '2025-12-12', 'M')` returns `14` + +!!! abstract "day" + Returns the day of a `date` as a number. The returned number is between 1 and 31. + + ``` + day(date) + ``` + + __Example__ `day('2025-01-03')` returns `3` + +!!! abstract "eomonth" + Determines the date of the last day of `n`th month before or after (depending on the sign of `n`) the specified date (`startDate`). If `n` is 0, the last day of the month is simply determined. + + ``` + eomonth(startDate, n) + ``` + + __Example__ `eomonth('2025-01-01', 1)` returns `2025-02-28T00:00:00+02:00` + + + + __Example__ `eomonth('2025-01-01', -1)` returns `2024-12-31T00:00:00+02:00` + +!!! abstract "hour" + Returns the hour of a `date` as a number. The number returned is between 0 and 23. + + ``` + hour(date) + ``` + + __Example__ `hour('2025-02-14 13:14:52')` returns `13` + + If no hour is contained in the time specification (`date`), 0 is returned. + + + __Example__ `hour('2025-02-14')` returns `0` + +!!! abstract "hours" + Returns the number of hours between two date values (`startDate` and `endDate`). The minutes in the date values are not taken into account. + + ``` + hours(startDate, endDate) + ``` + + __Example__ `hours('2025-02-14 13:14', '2025-02-14 15:14')` returns `2` + + If no hours are included in the time specification (`startDate` or `endDate`), 0 o'clock on this day is automatically assumed. + + + __Example__ `hours('2020-02-14', '2020-02-14 15:14')` returns `15` + +!!! abstract "minute" + Returns the minutes of a time specification (`date`) as a number. The number returned is between 0 and 59. + + ``` + minute(date) + ``` + + __Example__ `minute('2025-02-14 13:14:52')` returns `14` + + If no minutes are included in the time (`date`), 0 is returned. + + + __Example__ `minute('2025-02-14)` returns `0` + +!!! abstract "month" + Returns the month of a `date` as a number. The returned number is between 1 (January) and 12 (December). + + ``` + month(date) + ``` + + __Example__ `month('2025-02-14 13:14:52')` returns `2` + +!!! abstract "months" + Returns the number of months between two date values (`startDate` and `endDate`). The days and time in the date values are not taken into account. + + ``` + months(startDate, endDate) + ``` + + __Example__ `months('2025-02-01 13:14', '2025-03-31 15:54')` returns `1` + + +!!! abstract "networkdays" + Returns the number of full working days between two dates (`startDate` and `endDate`). You can also define holidays other than Saturday and Sunday (`holiday1`, `holiday2`, etc.), which are also deducted. If you do not want to include public holidays, you can simply omit these parameters. + + ``` + networkdays(startDate, endDate, holiday1, holiday2, ...) + ``` + + __Example__ `networkdays('2025-01-01', '2025-01-07','2025-01-01')` returns `4` + + Please note that the specified last day (`endDate`) is also included in the formula. Thus, for the following example, three working days are counted: the 7th, 8th and 9th of September, 2025. + + + __Example__ `networkdays('2025-09-08', '2025-09-10')` returns `3` + +!!! abstract "now" + Returns the current date and time. + + ``` + now() + ``` + + __Example__ `now()` returns `2025-09-07T12:59+02:00` + +!!! abstract "second" + Returns the seconds of a time (`date`) as a number. The number returned is between 0 and 59. + + ``` + second(date) + ``` + + __Example__ `second('2025-02-14 13:14:52')` returns `52` + +!!! abstract "today" + Returns the current date. + + ``` + today() + ``` + + __Example__ `today()` returns `2020-09-07T00:00:00+02:00` + + This function is handy for calculating time between a certain datetime and now. On each reload of the Base or recalculation, the calculation is updated. + + __Example__ `networkdays('2025-10-01', today())` returns `4` + +!!! abstract "weekday" + Returns the weekday of a `date` as a number. The returned number between 1 and 7, where you can define the first day of the week (`weekStart`). `weekStart` is Sunday by default, it can also be set to Monday ('Monday' or 'monday', not case sensitive). + + ``` + weekday(date, weekStart) + ``` + + __Example__ `weekday('2025-01-01', 'Monday')` returns `3` + + If no `weekStart` is given or if a `weekStart` other than 'Monday' or 'Sunday' is given, the default value ('Sunday') is used. So if it should be 'Monday', enter 'Monday'; if it should be 'Sunday', you can omit this parameter. + + + __Example__ `weekday('2025-01-01', 'Thursday') OR weekday('2025-01-01')` returns `4` + +!!! abstract "weeknum" + Returns the absolute week number of a `date` as a number. The returned number is between 1 and 53, where you can define the first day of the week (`return_type`). Enter the number 1 or 2, or 11 to 17, and 21 as `return_type` to define the start of a week: 1/Sunday, 2/Monday, 11/Monday, 12/Tuesday, 13/Wednesday, 14/Thursday, 15/Friday, 16/Saturday, 17/Sunday. If you want the week number to be returned according to ISO standard, specify the number of 21 as `return_type`, or use the function `isoweeknum`. + + ``` + weeknum(date, return_type) + ``` + + __Example__ `weeknum('2025-01-12', 11)` returns `2` + + If no '`return_type`' is given, it is always assumed to be 'Sunday'. + + + __Example__ `weeknum('2025-01-12')` returns `3` + +!!! abstract "year" + Returns the year of a `date` as a number. + + ``` + year(date) + ``` + + __Example__ `year('2025-01-01')` returns `2025` + +!!! abstract "startofweek" + Returns the first day of the week in which the `date` is located. `weekStart` is Sunday by default, it can also be set to Monday ('Monday' or 'monday', not case sensitive). + + ``` + startofweek(date, weekStart) + ``` + + __Example__ `startofweek('2025-04-28')` returns `2021-4-27T00:00:00+02:00` + +!!! abstract "quarter" + Returns the quarter of the `date`, the return value is 1, 2, 3, 4. + + ``` + quarter(date) + ``` + + __Example__ `quarter('2025-01-01')` returns `1` + +!!! abstract "isodate" + Returns the ISO string representation of the `date`. + + ``` + isodate(date) + ``` + + __Example__ `isodate('2025-01-01 11:00:00')` returns `2025-01-01` + +!!! abstract "isomonth" + Returns the ISO string representation (of the )year and month) of the month of a specified `date`. + + ``` + isomonth(date) + ``` + + __Example__ `isomonth('2025-01-01 11:00:00')` returns `2025-01` + + +## Geo functions + +!!! abstract "country" + Returns the country or region of a geolocation-type column. (Since version 5.1.0) + + ``` + country(geolocation) + ``` + + __Example__ ```country(`Country of residence`)``` returns `Germany` + +## Logical functions + +!!! abstract "and" + Checks if all arguments (`logical1`, `logical2`, ...) are true (valid, not empty and not equal to zero). If yes, `true` is returned, otherwise `false`. + + ``` + and(logical1, logical2, ...) + ``` + + __Example__ `and(1, '', 2)` returns `false` + +!!! abstract "if" + Checks if an argument (`logical`) is true and returns `trueValue` or `falseValue` accordingly. + + ``` + if(logical, trueValue, falseValue) + ``` + + __Example__ `if(1>2, 3, 4)` returns `4` + + For the condition (`logical`) only a comparison is allowed. If `falseValue` is omitted`: it will return the first value (`trueValue`) if the condition (`logical`) is true; and it will return an empty value ('') if the condition (`logical`) is false. + + + __Example__ `if(`Budget`>`Price`, 'Yes')` returns `Yes` or '' + +!!! abstract "ifs" + Checks if one or more conditions (`logical1`, `logical2`, ...) are true and returns a value (`value1`, `value2`, ...) that matches the **first** true condition. + + ``` + ifs(logical1, value1, logical2, value2, ...) + ``` + + __Example__ `ifs( 1>2, 3, 5>4, 9)` returns `9` + +!!! abstract "not" + Inverts the logical value (`boolean`). In other words: converts true to false and false to true. + + ``` + not(boolean) + ``` + + __Example__ `not(and(1, '', 2))` returns `true` + +!!! abstract "or" + Checks if at least 1 of the arguments (`logical1`, `logical2`, ...) is true (valid, not empty and not equal to zero), and returns `true` in this case. If all arguments are false, then returns `false`. + + ``` + or(logical1, logical2, ...) + ``` + + __Example__ `or(1,'',2)` returns `true` + +!!! abstract "switch" + Evaluates an expression (`logical`) against a list of values (matcher) and returns the result (value) corresponding to the **first** matching value. If there is no match, an optional `default` value is returned. At least 3 parameters (`logical`, matcher, value) must be specified. + + ``` + switch(logical, matcher1, value1, matcher2, value2, ..., default) + ``` + + __Example__ `switch(`grades`, 1, 'very good', 2, 'good', 3, 'satisfactory', 4, 'passed', 'failed')` returns `very good` + + If there are several identical values in the value list (matcher), only the first hit is taken into account. + + + __Example__ `switch(int(68/10), 6, 'OK', 6, 'KO')` returns `OK` + +!!! abstract "xor" + Returns the logical inequality of all arguments. In other words, returns `true` fi the number of true arguments is odd. + + ``` + xor(logical1, logical2, ...) + ``` + + __Example__ `xor(1, 0, 2<1)` returns `true` + +## Statistical functions + +!!! abstract "average" + Returns the average of the numbers (`number1`, `number2`, ...). + + ``` + average(number1, number2, ...) + ``` + + __Example__ `average(1, 2, 3, 4, 5)` returns `3` + +!!! abstract "counta" + Counts the number of non-empty cells (`textORnumber1`, `textORnumber2`, ...). These cells can be text or numbers. In this example, 1 and 2 are numbers, '3' is text, and '' is an empty value. + + ``` + counta(textORnumber1, textORnumber2, ...) + ``` + + __Example__ `counta(1, '', 2, '3')` returns `3` + +!!! abstract "countall" + Counts the number of elements (`textORnumber1`, `textORnumber2`, ...) including numbers (1, 2), text ('3') and empty cells (''). + + ``` + countall(textORnumber1, textORnumber2, ...) + ``` + + __Example__ `countall(1, '', 2, '3')` returns `4` + +!!! abstract "countblank" + Counts the number of empty cells. + + ``` + countblank(textORnumber1, textORnumber2, ...) + ``` + + __Example__ `countblank(1, '', 2, '3')` returns `1` -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :-------------------------------- | -| add(num1,num2) | Adds two numeric values (num1 and num2) and returns the result. | add(1,2) | 3 | -| subtract(num1,num2) | Subtracts one numeric value (num2) from another (num1). | subtract(5,4) | 1 | -| multiply(num1,num2) | Multiplies two numeric values. | multiply(3,4) | 12 | -| divide(num1,num2) | Divides one numeric value (num1) by another (num2). | divide(3,2) | 1.5 | -| mod(num1,num2) | Calculates the remainder of a division. | mod(15,7) | 1 | -| power(num1,num2) | Calculates the power (num2) of a number (num1). | power(3,2) | 9 | -| greater(num1,num2) | Checks if a numeric value (num1) is greater than another (num2) and returns the logical value 'true' or 'false'. | greater(2,3) | false | -| lessthan(num1,num2) | Checks if a numeric value (num1) is less than another (num2) and returns the logical value 'true' or 'false'. | lessthan(2,3) | true | -| greatereq(num1,num2) | Checks whether a numeric value (num1) is greater than or equal to another (num2) and returns the logical value 'true' or 'false'. | greatereq(2,3) | false | -| lessthaneq(num1,num2) | Checks whether a numeric value (num1) is less than or equal to another (num2) and returns the logical value 'true' or 'false'. | lessthaneq(2,3) | false | -| equal(num1,num2) | Checks if two values (number1, number 2) are equal and returns the logical value 'true' or 'false'. | equal(\`Old price\`, \`New price\`) | false | -| unequal | Checks whether two values (number1, number2) are not equal and returns the logical value 'true' or 'false'. | unequal(\`Old price\`, \`New price\`) | true | -| concatenate(string1, string2, ...) | Combines several character strings (string1, string 2, ...) into one character string. | concatenate(\`Supplier\`, " has the product ", \`Product\`) | Microsoft has the product Windows | - -### Mathematical functions - -Parameters must be numbers. If string is passed to a parameter, it'll be converted to number. - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------- | :--------- | -| abs(number) | Returns the absolute value of a number. | abs(-2) | 2 | -| ceiling(number, significance) | Rounds a number to the nearest integer or to the nearest multiple of the specified significance. If either argument is non-numeric, the formula returns an empty value. | ceiling(2.14) | 3 | -| | If the number is an exact multiple of the significance, then no rounding occurs. If the number and the significance are negative, then the rounding is away from 0. If the number is negative and the significance is positive, then the rounding is towards 0. | ceiling(-2.14, 4) | 0 | -| even(number) | Assigns a real number to the nearest larger even number. | even(2.14) | 4 | -| exp(number) | Exponential function for Euler's number e. Returns the value of e given high (number). | expr(1) | 2.71828... | -| floor(number, significance) | Rounds a number to the nearest integer or to the nearest multiple of the specified significance. If either argument is non-numeric, the formula returns an empty value. | floor(2.86) | 2 | -| | If the number is an exact multiple of the significance, then no rounding takes place. If the sign of the number is positive, then the rounding is towards 0. If the sign of the number is negative, then the rounding is away from 0. | floor(-3.14, 5) | \-5 | -| int(number) | Assigns the nearest smaller integer to a real number. | int(-3.14) | \-4 | -| lg(number) | Logarithm function (number) with 10 as base. | lg(100) | 2 | -| log(number, base) | Logarithm function (number) with definable base. | log(81, 3) | 4 | -| | But if no base is given, this function works exactly like lg(), with 10 as base. | log(1000) | 3 | -| odd(number) | Assigns a real number to the nearest larger odd number. | odd(-2.14) | \-1 | -| round(number, digits) | Rounds a number to the nearest integer. If no decimal place (digits) is specified, the number is rounded to the 1st digit to the left of the decimal point. | round(3.14) | 3 | -| | If a positive decimal place (digits) is given, the digit to the right of the decimal point is rounded. | round(3.14, 1) | 3.1 | -| | If a negative decimal place (digits) is given, is rounded to the left of the decimal point. | round(3.14, -3) | 0 | -| rounddown(number, digits) | Rounds a number towards zero. If no decimal place (digits) is given, the number is rounded to the 1st digit left of the decimal point. | rounddown(3.12, 1) | 3.1 | -| roundup(number, digits) | Rounds a number from zero to the nearest whole number. If no decimal place (digits) is given, the number is rounded to the 1st digit left of the decimal point. | roundup(-3.15) | \-4 | -| sign(number) | Checks whether a number is greater, equal or less than 0. Returns the values 1, 0 and -1 respectively. In other words: it returns the sign of a number, for '+', 'zero' and '-' with 1, 0, and -1 respectively. | sign(-2) | \-1 | -| sqrt(number) | Returns the square root of a number. | sqrt(81) | 9 | - -### Text functions - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------ | :--------------------------- | -| exact(string1, string2) | Checks whether two character strings (string1, string2) are exactly identical. Returns the values 'true' or 'false' respectively. Case sensitive. | exact('SeaTable', 'Seatable') | false | -| find(findString, sourceString, startPosition) | Returns the start position of a string (findString) within another string (sourceString). It is case sensitive. Without find, 0 is returned. If the start position (startPosition) is given as decimal, it is rounded down. If the cell in the column for the keyword (findString) is still empty, 1 is returned. If the cell in the column for the target string (sourceString) is still empty, an empty value ('') is returned. | find('Sea', 'seaTable', 1) | 0 | -| | The search will start from the given 'startPosition'. This 'startPosition' has no influence on the result: it always returns the absolute start position. If the 'startPosition' of the character string to be searched for (findString) is given after the actual start position of the character string (sourceString), 0 is returned, since nothing was found from this position. | find('table', 'big table', 4) | 5 | -| left(string, count) | Returns the specified number (count) of characters at the beginning of a string. | left('SeaTable', 3) | Sea | -| len(string) | Returns the number of characters in a string. | len('SeaTable') | 8 | -| lower(string) | Converts a character string to lower case letters. | lower('German) | german | -| mid(string, startPosition, count) | Returns the specified number (count) of characters from the specified start position (startPosition) of a string. | mid('SeaTable is the best', 1, 8) | SeaTable | -| | Start position (startPosition) and count must not be empty, negative or zero. However, if start position (startPosition) and number (count) are given as decimal, they are rounded down. Too much count is ignored. | mid('SeaTable is the best.', 10.9, 27.3) | is the best. | -| replace(sourceString, startPosition, count, newString) | Replaces a part (count) of a character string (sourceString) from a certain start position (startPosition) with another character string (newString). The number (count) of characters is only taken into account for the old string (sourceString), but not for the new string (newString). | replace('SeaTable is the best.', 1, 8, 'Seafile') | Seafile is the best. | -| | If number (count) is given as zero, the new string (newString) is simply added to the old string (sourceString) from the start position (startPosition). | replace('SeaTable is the best.', 1, 0, 'Seafile') | SeafileSeaTable is the best. | -| rept(string, number) | Repeats a string as often (number) as specified. | rept('Sea ', 3) | SeaSeaSea | -| right(string, count) | Returns the specified number (count) of characters at the end of a string. | right('SeaTable', 5) | Table | -| search(findString, sourceString, startPosition) | Returns the start position of a string (findString) within another string (sourceString). It is not case-sensitive. Without find, 0 is returned. If the start position (startPosition) is given as decimal, it is rounded down. If the cell in the column for the keyword (findString) is still empty, 1 is returned. If the cell in the column for the target string (sourceString) is still empty, an empty value ('') is returned. | search('Sea', 'seaTable', 1) | 1 | -| | The search will start from the given 'startPosition'. This 'startPosition' has no influence on the result: it always returns the absolute start position. If the 'startPosition' of the character string to be searched for (findString) is given after the actual start position of the character string (sourceString), 0 is returned, since nothing was found from this position. | search('table', 'big table', 6) | 0 | -| substitute(sourceString, oldString, newString, index) | Replaces existing text (oldString) with new text (newString) in a string (sourceString). If there is more than one text (oldString) in the string (sourceString), only the 'index'-th text is replaced. The text is case-sensitive. | substitute('SeaTableTable', 'Table', 'file', 1) | SeafileTable | -| | If the index is given as 0 or not, all found text (oldString) will be replaced by the new text (newString). | substitute('SeaTableTable', 'Table', 'file') | Seafilefile | -| T(value) | Checks whether a value is text. If so, the text is returned. If no, the return value is empty. | T('123') | 123 | -| text(number, format) | Converts a number into text and formats it in the specified format. The format can be percent and number as well as dollar, euro and yuan. | text(150, 'euro') | €150 | -| | When a number is converted directly to percent, its absolute value is retained. In other words, 50 is converted into 5000%. But if you want 50%, you have to divide the number by 100 before the conversion. | text(50, 'percent') | 5000% | -| trim(string) | Removes spaces at the beginning and end of a string. | trim(' SeaTable ') | SeaTable | -| upper(string) | Converts a string to uppercase letters. | upper('German) | GERMAN | -| value(string) | Converts a text (string) representing a number into a number. | value('123') | 123 | - -### Date functions - -When passing a parameter with time or date type, you can specify a contant in "2021-09-01 12:00:01" or "2021-09-01" format. When you query the result of a date function in SQL, the result will be converted to a string in RFC3339 format, e.g. "2021-09-03T00:00:00+02:00". Please note that if a date function returns a date type, it cannot be used as parameter for text or maths functions. - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | :------------------------ | -| date(year, month, day) | Returns a date in international format (ISO) from entered year, month and day. If the year is entered with two digits, it is automatically understood as a year in the 1900s. If the number of the month or day is too large (greater than 12 or 31 respectively), these months or days are automatically converted to the next year or month. | date(2021, 1, 3) | 2021-01-03T00:00:00+02:00 | -| dateAdd(date, count, unit) | Adds the specified number (count) of years ('years'), months ('months'), weeks ('weeks'), days ('days'), hours ('hours'), minutes ('minutes') or seconds ('seconds') to a date/time ('date'). | dateAdd('2020-02-03', 2, 'days') | 2020-02-05T00:00:00+02:00 | -| | Tip: if you want to add a complex duration (count) such as 1 day 12 hours, you can convert it to e.g. 24+12=36 hours ('hours') and enter it into the formula as a uniform duration (count). The duration is converted to the smallest unit: in this case, hours. | dateAdd('2020-09-04 13:05:18', 36, 'hours') ODER dateAdd(\`form submission\`, 36, 'hours') | 2020-09-06T01:05:18+02:00 | -| datedif(startDate, endDate, unit) | Calculates the seconds, days, months, or years between two date values. The optional unit argument can be one of the following: S (seconds), D (full days), M (full months), Y (full years), YD (full days, ignoring years), YM (full months, ignoring days and years), MD (full days, ignoring months and years). If the startDate is empty, a default value of "1900-01-01" will be set. If both date values are empty, it will return 0. | dateDif('2018-01-01', '2020-01-01') | 2 | -| | The optional unit argument can be one of the following: S (seconds), D (full days), M (full months), Y (full years), YD (full days, ignoring years), YM (full months, ignoring days and years), MD (full days, ignoring months and years). | dateDif('2019-10-11', '2020-12-12', 'M') | 14 | -| day(date) | Returns the day of a date as a number. The returned number is between 1 and 31. | day('2020-01-03) | 3 | -| eomonth(startDate, months) | Determines the date of the last day of the month that is the specified number (months) of months after the specified date (startDate). If the number (months) is given as 0, the last day of the month is simply determined. | eomonth('2020-01-01', 1) | 2020-02-29T00:00:00+02:00 | -| | If the number (months) is given as negative, the date of the last day of the month that contains the absolute number (months) of months before the specified date (startDate) is determined. | eomonth('2020-01-01', -1) | 2019-12-31T00:00:00+02:00 | -| hour(date) | Returns the hour of a date as a number. The number returned is between 0 and 23. | hour('2020-02-14 13:14:52) | 13 | -| | If no hour is contained in the time specification (date), 0 is returned. | hour('2020-02-14) | 0 | -| hours(startDate, endDate) | Returns the number of hours between two date values (startDate and endDate). The minutes in the date values are not taken into account. | hours('2020-02-14 13:14', '2020-02-14 15:14') | 2 | -| | If no hours are included in the time specification (startDate or endDate), 0 o'clock on this day is automatically assumed. | hours('2020-02-14', '2020-02-14 15:14') | 15 | -| minute(date) | Returns the minutes of a time specification (date) as a number. The number returned is between 0 and 59. | minute('2020-02-14 13:14:52 | 14 | -| | If no minutes are included in the time (date), 0 is returned. | minute('2020-02-14) | 0 | -| month(date) | Returns the month of a date as a number. The returned number is between 1 (January) and 12 (December). | month('2020-02-14 13:14:52) | 2 | -| months(startDate, endDate) | Returns the number of months between two date values (startDate and endDate). The days and time in the date values are not taken into account. | months('2020-02-01 13:14', '2020-03-31 15:54') | 1 | -| | If no month is given in the date values (startDate, endDate), January is automatically assumed to be the month. | months('2020', '2021') | 12 | -| networkdays(startDate, endDate, holiday1, holiday2, ...) | Returns the number of full working days between two dates (startDate and endDate). You can also define holidays other than Saturday and Sunday (holiday1, holiday2, etc.), which are also deducted. If you do not want to include public holidays, you can simply omit these parameters. | networkdays('2020-01-01', '2020-01-07','2020-01-01') | 4 | -| | Please note that the specified last day (endDate) is also included in the formula. That means, as in this formula, three working days are counted: the 7th, 8th and 9th of September, 2020. | networkdays('2020-09-07', '2020-09-09') | 3 | -| now() | Returns the current date and time. This column is only updated automatically when the Base is reloaded. | now() | 2020-09-07T12:59+02:00 | -| second(date) | Returns the seconds of a time (date) as a number. The number returned is between 0 and 59. | second('2020-02-14 13:14:52') | 52 | -| today() | Returns the current date. This column is only updated automatically if the Base has been reloaded. | today() | 2020-09-07T00:00:00+02:00 | -| | This function is handy for calculating time between a certain date & time and now. On each reload or recalculation of the Base, the calculation is updated. | networkdays('2020-09-01', today()) | 4 | -| weekday(date, weekStart) | Returns the weekday of a date as a number. The returned number between 1 and 7, where you can define the first day of the week (weekStart): Monday ('Monday') or Sunday ('Sunday' or omitted, since the start as Sunday is the default). A third option is not possible. Upper/lower case is not considered. | weekday('2020-01-01', 'Monday') | 3 | -| | If no 'weekStart' is given or if a 'weekStart' other than 'Monday' or 'Sunday' is given, it is always assumed to be 'Sunday'. So if it should be 'Monday', enter 'Monday'; if it should be 'Sunday', you can omit this parameter. | weekday('2020-01-01', 'Thursday') OR weekday('2020-01-01') | 4 | -| weeknum(date, return_type) | Returns the absolute week number of a date as a number. The returned number is between 1 and 53, where you can define the first day of the week (return_type). Enter the number 1 or 2, or 11 to 17, and 21 as "return_type" to define the start of a week: 1/Sunday、2/Monday、11/Monday、12/Tuesday、13/Wednesday、14/Thursday、15/Friday、16/Saturday、17/Sunday. If you want the week number to be returned according to ISO standard, specify the number of 21 as "return_type", or use the function isoweeknum. | weeknum('2020-01-12', 11) | 2 | -| | If no 'return_type' is given, it is always assumed to be 'Sunday'. | weeknum('2020-01-12') | 3 | -| year(date) | Returns the year of a date as a number. | year('2020-01-01') | 2020 | -| startofweek(date, weekStart) | Returns the first day of the week in which the date is located. WeekStart defaults to sunday, or it can be set to monday. | startofweek('2021-04-28') | 2021-4-25T00:00:00+02:00 | -| quarter(date) | Returns the quarter of the date, the return value is 1, 2, 3, 4. | quarter('2021-01-01') | 1 | -| isodate(date) | Returns the ISO string representation of the date. | isodate('2021-01-01 11:00:00') | 2021-01-01 | -| isomonth(date) | Returns the ISO string representation of the year and month | isomonth('2021-01-01 11:00:00') | 2021-01 | - -### Geo functions - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :-------------------- | :-------------------------------------------- | :-------------------- | :----- | -| country(geolocation) | Returns the country or region of a Geolocation column. (Since version 5.1.0) | country(column_name) | Germany | - -### Logical functions - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :-------- | -| and(logical1, logical2, ...) | Checks if all arguments (logical1, logical2, ...) are true (valid, not empty and not equal to zero). If yes, 'true' is returned, otherwise 'false'. | and(1, '', 2) | false | -| if(logical, value1, value2) | Checks if an argument (logical) is true and if yes, returns the first value (value1) and if no, returns the second value (value2). | if(1>2, 3, 4) | 4 | -| | For the condition (logical) only a comparison with is allowed. If you enter only condition (logical) and the first value (value1): it will return the first value (value1) if the condition (logical) is true; and it will return an empty value ('') if the condition (logical) is false. | if(\`Budget\`>\`Price\`, 'Yes') | Yes | -| ifs(logical1, value1, logical2, value2, ...) | Checks if one or more conditions (logical1, logical2, ...) are true and returns a value (value1, value2, ...) that matches the first TRUE condition. | ifs( 1>2, 3, 5>4, 9) | 9 | -| not(boolean) | Inverts the logical value (boolean). In other words: converts 'true' to 'false' and 'false' to 'true'. | not(and(1, '', 2)) | true | -| or(logical1, logical2, ...) | Checks if at least 1 of the arguments (value1, value2, ...) is true (valid, not empty and not equal to zero), and returns 'true' in this case. If all arguments are false, then returns 'false'. | or(1,'',2) | true | -| switch(logical, matcher1, value1, matcher2, value2, ..., default) | Evaluates an expression (logical) against a list of values (matcher) and returns the result (value) corresponding to the first matching value. If there is no match, an optional default value is returned. At least 3 parameters (logical, matcher, value) must be specified. | switch(\`grades\`, 1, 'very good', 2, 'good', 3, 'satisfactory', 4, 'passed', 'failed') | very good | -| | If there are several identical values in the value list (matcher), only the first hit is taken into account. | switch(int(68/10), 6, 'OK', 6, 'KO') | OK | -| xor(logical1, logical2, ...) | Returns the contravalence of all arguments. In other words, checks if the number of true arguments is (logical) odd and returns 'true'. | xor(1, 0, 2\<1) | false | - -### Statistical functions - -| OPERATOR | DESCRIPTION | INPUT | RESULT | -| :-------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | :----- | -| average(number1, number2, ...) | Returns the average of the numbers (number1, number2, ...) | average(1, 2, 3, 4, 5) | 3 | -| counta(textORnumber1, textORnumber2, ...) | Counts the number of non-empty cells (textORnumber1, textORnumber2, ...). These cells can be text or numbers. In this example, 1 and 2 are numbers, '3' is text, and '' is an empty value. | counta(1, '', 2, '3') | 3 | -| countall(textORnumber1, textORnumber2, ...) | Counts the number of elements (textORnumber1, textORnumber2, ...) including numbers (1, 2), text ('3') and empty cells (''). | countall(1, '', 2, '3') | 4 | -| countblank(textORnumber1, textORnumber2, ...) | Counts the number of empty cells. | countall(1, '', 2, '3') | 1 | -| countItems(column) | Counts the number of items in a column. The supported column types are multiple select, collaborator, file, image. (available since version 2.7.0) | countItems(column_name) | 2 | +!!! abstract "countItems" + Counts the number of items in a `column`. The supported `column` types are multiple select, collaborator, file, image (available since version 2.7.0). + + ``` + countItems(column) + ``` + + __Example__ `countItems(column_name)` returns `2` diff --git a/docs/scripts/sql/introduction.md b/docs/scripts/sql/introduction.md new file mode 100644 index 00000000..cdea3e9b --- /dev/null +++ b/docs/scripts/sql/introduction.md @@ -0,0 +1,295 @@ +# SQL in SeaTable + +SQL queries are the most powerful way access data stored in a base. If your not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). If some tables in a base are archived, archived rows are also queried, as well as rows that are not archived yet. + +!!! info "Backticks for table or column names containing or special characters or using reserved words" + For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. + + Similarly, if some of your of table or column names are the same as [SQL function](./functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. + +## Supported SQL Syntax + +Currently only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are supported (the last three require version 2.7 or later). You'll find below the syntax for these statements. + +Please note that the SQL syntax is case insensitive: we use only upper-cased instructions here for ease of reading (differentiating SQL instructions from table or column names). + +### Retrieving row(s) + +!!! abstract "SELECT" + The `SELECT` statement allows you to retrieve an eventually filtered, sorted and/or grouped list of the rows from a specific table. Each returned row is a JSON object. The keys of the object are the column **keys, NOT the column names**. To use column names as keys, the `convert_keys` parameter (available since version 2.4) in query request should be `true` (which is the default value when using `base.query` for both JavaScript and Python scripts). + The syntax of `SELECT` statement is: + + ``` + SELECT [Column List] FROM tableName [Where Clause] [Group By Clause] [Having Clause] [Order By Clause] [Limit Option] + ``` + + `[Column List]` is the list of columns you want to retrieve, separated by commas. If you want to retrieve all the columns, you can use a wildcard (`*`). + You can consult specific sections for [Where, Group By, Having or Order By clauses](#where-group-by-having-and-order-by-clauses) + `Limit Option` uses MySQL format. The general syntax is `LIMIT ... OFFSET ...`. This parameters are optional. Unless you specify a higher limit, the method returns **a maximum of 100 rows**. The maximum number of rows returned is **10000** no matter the limit specified in the SQL statement. The `OFFSET` will help you retrieving the following rows in other queries + + __Example__ `SELECT * FROM Table1 LIMIT 10000` returns the first 10000 rows, `SELECT * FROM Table1 LIMIT 10000 OFFSET 10000` returns the next 10000 rows + + + Since version 4.3, basic **implicit** *join* query is supported, for example: + + ``` + SELECT ... FROM Table1, Table2 WHERE Table1.column1 = Table2.column2 AND ... + ``` + + The *join* queries have the following restrictions: + + - You **must not** explicitly write JOIN keyword + - Only *inner join* is supported; *left join*, *right join*, and *full join* are not supported. + - Tables in the `FROM` clause should be unique (no duplicate tables). + - Each table in the `FROM` clause should be associated with at least one join condition. + - Join conditions should be placed in the `WHERE` clause, and eventually connected with one or more `AND` operators. + - Join conditions can only use **equality operator** on columns, e.g. `Table1.column1 = Table2.column2`. + - Columns in join conditions must be indexed, unless the table is not archived. + +!!! info "Field aliases" + Field alias with `AS` syntax is supported. For example, `SELECT table.a as a FROM table` returns rows whose first column is keyed by "a". There are two important points to note however: + + - Field alias can be referred in `GROUP BY`, `HAVING` and `ORDER BY` clauses. For example, `SELECT i.amount AS a, COUNT(*) FROM Invoices AS i GROUP BY a HAVING a > 100` is valid. + - Field alias cannot be referred in `where` clause. E.g., `select t.registration as r, count(*) from t group by r where r > "2020-01-01"` will report syntax error. + +!!! info "Aggregation functions" + While retrieving rows, you can add aggregation functions to the list of columns if you specify a [GroupByClause](#where-group-by-having-and-order-by-clauses). The available functions are: + + - `COUNT` returns the number of non-empty values in a specific column or for all columns with `COUNT(*)` + - `SUM` computes the sum of values in a specific column, for example `SUM(Invoices.Amount)` + - `MAX` retrieves the greatest value in a specific column, for example `MAX(Invoices.Amount)` + - `MIN` retrieves the smallest value in a specific column, for example `MIN(Invoices.Amount)` + - `AVG` computes the average of non-empty values in a specific column, for example `AVG(Invoices.Amount)` + + __Example__ + + ``` + SELECT Customer, SUM(Amount) from Invoices GROUP BY Customer + ``` + +### Modifying database content + +!!! abstract "INSERT" + + !!! warning "Enterprise subscription needed" + + `INSERT` requires [Big Data](https://seatable.com/help/big-data-capabilities/) storage support, which is available only with an [Enterprise subscription](https://seatable.com/help/subscription-plans/#seatable-cloud-enterprise-search). + + `INSERT` allows you to append a new row to a table. `INSERT` statement **only** supports bases that have been [archived](https://seatable.com/help/aktivieren-des-big-data-backends-in-einer-base/#designation-as-archivebackend-search). The rows will be inserted into big-data storage. It'll return error if the base is not archived yet. + + + If you want to insert rows in a non-archived base, please use the API dedicated functions (e.g. the [Python API](../python/objects/rows.md#add-rows)). + + ``` + INSERT INTO table_name [column_list] VALUES value_list [, ...] + ``` + + - `column_list` is a list of column names surrounded by parentheses. If omitted, it defaults to all updatable columns. + - `value_list` is a list of values surrounded by parentheses. Values must be in the same order as the column list, for example: `(1, "2", 3.0)`. + - Columns with multiple values, such as "multiple select"-type column , requires values to be surrounded by parentheses, for example: `(1, "2", 3.0, ("foo", "bar"))`. + - Values of "single select" and "multiple select"-type columns must be option names, not option keys. + - Few column types are **not allowed** to insert: + + - built-in columns, such as `_id`, `_ctime`. + - image, file, formula, link, link-formula, geolocation, auto-number, button + + __Example__ + + ``` + INSERT INTO Table1 (Name, Age) values ('Erika', 38) + ``` + +!!! abstract "UPDATE" + `UPDATE` allows you to update one or multiple existing rows of a table. Unlike the `INSERT` statement, `UPDATE` allows you to update rows in both normal and big-data storage. `WhereClause` is optional. However, keep in mind that if omitted, **all rows** will be updated! + + ``` + UPDATE table_name SET column_name = value [, ...] [WhereClause] + ``` + + - Columns with multiple values, such as "multiple select"-type column , requires values to be surrounded by parentheses, for example: `("foo", "bar")`. + - Values of "single select" and "multiple select"-type columns must be option names, not option keys. + - Few column types are **not allowed** to update: + + - built-in columns, such as `_id`, `_ctime`. + - image, file, formula, link, link-formula, geolocation, auto-number, button + + __Example__ + + ``` + UPDATE INTO Contacts SET Adult=true WHERE Age>=18 + ``` + + __Example__ + + ``` + UPDATE INTO Contacts SET Adult=true, `Age group`="18+" WHERE Age>=18 + ``` + + If `Age group` is a "single select"-type column, the option you want to select (here "18+") has to exist already. + +!!! abstract "DELETE" + `DELETE` allows you to delete one or multiple existing rows of a table. Unlike the `INSERT` statement, `DELETE` allows you to delete rows in both normal and big-data storage. `WhereClause` is optional. However, keep in mind that if omitted, **all rows** will be deleted! + + ``` + DELETE FROM table_name [WhereClause] + ``` + + __Example__ + + ``` + DELETE FROM Contacts WHERE Age<18 + ``` + + +### WHERE, GROUP BY, HAVING and ORDER BY clauses + +!!! abstract "WHERE clause" + Most SQL syntax can be used in the `WHERE` clause, including arithmetic expressions, comparison operators, `[NOT] LIKE`, `IN`, `BETWEEN ... AND ...`, `AND`, `OR`, `NOT`, `IS [NOT] TRUE`, `IS [NOT] NULL`. + + - Arithmetic expressions only support numbers. + - Time constants should be strings in ISO format (e.g. "2020-09-08 00:11:23"). Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). + +!!! abstract "GROUP BY clause" + `GROUP BY` uses strict syntax. The selected fields must appear in the clause list, except for aggregation functions (`COUNT`, `SUM`, `MAX`, `MIN`, `AVG`) and formulas (see extended syntax section below). + +!!! abstract "HAVING clause" + `HAVING` filters rows resulting from the `GROUP BY` clause. Only fields referred in the `GROUP BY` clause or aggregation functions (such as "SUM") can be used in `HAVING` clause. Other syntax is the same as specified for the `WHERE` clause. + +!!! abstract "ORDER BY clause" + Fields in `ORDER BY` list must be a column or an expression in the selected fields. For example, `select a from table order by b` is invalid; while `select a, b from table order by b` and `select abs(a), b from table order by abs(a)` are valid. + +### LIKE and BETWEEN operators + +!!! abstract "LIKE operator" + `LIKE` only supports strings. The key word `ILIKE` can be used instead of `LIKE` to make the match case insensitive. The percent sign `%` you will use in the `LIKE` expression represents zero, one, or multiple characters. + + __Example__ + ``` + SELECT `Full Name` from Contacts WHERE `Full Name` LIKE "% M%" + ``` + returns every records with a last name starting with M (considering that the `Full Name` fields is actually composed like "`First Name` `Last Name`") + +!!! abstract "BETWEEN operator" + `BETWEEN lowerLimit AND upperLimit` only supports numbers and time. `lowerLimit` and `upperLimit` are included in the search. They have to be in the right order (if `upperLimit`<`lowerLimit`, no records will be found). + + __Example__ + ``` + SELECT * from Contacts WHERE Age BETWEEN 18 AND 25 + ``` + returns every records with a last name starting with M (considering that the `Full Name` fields is actually composed like `First Name` `Last Name`) + + +## Data types + +### SeaTable <> SQL mapping + +Below is the mapping of SeaTable column types to SQL data types. + +| SeaTable column type | SQL data type | Query result format | Use in WHERE clause | Use in GROUP BY / ORDER BY clause | +| :-------------------- | :--------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------- | +| text | String | | Supported | Supported | +| long-text | String | Raw text in Markdown format | Supported | Supported | +| number | Float | | Supported | Supported | +| single-select | String | Returned rows contain the option name. | Supported. Refer an option by its name. E.g. `WHERE single_select = "New York"`. | Order by the definition order of the options | +| multiple-select | List of strings | Returned rows contain the option names. | Supported. Refer an option by its name. E.g. `WHERE multi_select = "New York"`. More details in the "List types" section below. | More details in the "List types" section below. | +| checkbox | Boolean | | Supported | Supported | +| date | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | +| image | List of URL for images | A JSON array with image URLs as elements | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | +| file | Will be returned as JSON format string when queried. | Not supported | Not Supported | Not Supported | +| collaborator | List of user IDs | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | +| link to other records | List of linked rows | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | +| formula | The type depends on the return value of the formula. | Depends on the type of the return value | Depends on the type of the return value | Depends on the type of the return value | +| \_creator | User ID as string | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported | Supported | +| \_ctime | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | +| \_last_modifier | User ID as string | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported | Supported | +| \_mtime | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | +| auto number | String | | Supported | Supported | +| url | String | | Supported | Supported | +| email | String | | Supported | Supported | +| duration | Float | Returned in seconds | Supported | Supported | + +### List types + +In SeaTable, two categories of column types are list types (columns with multiple values): + +- Built-in list types: including multiple selection, image, file, collaborator and link to other records. +- Formula columns dealing with linked records (using either `{link.column}` or `lookup`) and link formula columns whose formula is `lookup`, `findmin` or `findmax`. + +When referring to a list-type column in a `WHERE` clause, the following rules apply, depending on the type for the list elements. If an operator is not listed below, it's unsupported. + +| Element Type | Operator | Rule | +| :------------ | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | +| string | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | +| string | `LIKE`, `ILIKE` | Always take the first element for comparison; if there is no element, use an empty string (""). + | +| string | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | +| string | =, != | Always take the first element for comparison; if there is no element, use an empty string (""). | +| float | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | +| float | =, !=, <, <=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | +| float | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | +| float | Arithmetics operations such as +, -, * or / | Use the first element for calculation. | +| Datetime | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | +| Datetime | =, !=, <, <=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | +| Datetime | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | +| bool | `IS TRUE` | Always take the first element for comparison; return false if there are no elements. +| linked record | | Follow the rules for the type of the display column. | + +When a list column is returned in a selected field, only the ten first elements are returned. + +When used in `GROUP BY` or `ORDER BY` clauses, the elements for each list will first be sorted in ascending order, then the lists will be sorted by the rules below: + +- Compare the elements one by one, list with smaller element is sorted before list with larger element. +- If all elements compared in step 1 are equal, shorter list is sorted before longer list. +- Otherwise the tow lists are equal. + +If a list column is passed as parameter to a formula, and the parameter expects a scalar value, the first element will be used. And if the element is a linked record, the value of its display column will be used. + +When applying aggregate functions (min, max, sum, avg) to a list column, if there is only 1 element in the list, use that element; otherwise this row will not be aggregated. + +### NULL values + +NULL value is distinct from 0. It represents a missing value. The following values are treated as NULL: + +- Empty cells in a table. +- Values which cannot be converted to the column type. +- Empty strings (""). This is different from standard SQL. +- Lists are treated as NULL based on the rules described in the "List Types" section. +- Functions or formula columns that return error. + +In the `WHERE` clause: + +- Arithmetics operations such as +, -, * or / on NULL values will return NULL. +- `!=`, `NOT LIKE`, `NOT IN`, `NOT BETWEEN`, `HAS NONE OF`, `IS NOT TRUE`, and `IS NULL` operations will return `true` when the value is NULL. +- `AND`, `OR`, `NOT` treat NULL values as `false`. +- Aggregate functions (min, max, sum, avg) will ignore NULL values. + +In formulas, NULL values will be converted to 0 or an empty string. + +## Extended syntax + +### Using formulas in SQL query + +You may use a formula syntax that's almost the same as SeaTable's formulas in SQL queries. There are a few special notes: + +- Link formulas are not supported. e.g. {link.age} is invalid. +- Reference to columns should not be enclosed by curly brackets ("{}"). Don't write `SELECT abs({column}) FROM table`. Write `SELECT abs(column) FROM table`. This is consistent with standard SQL syntax. +- You have to use backticks ("\`\`") to enclose column references, when column name contains space or "-". E.g. ```SELECT abs(`column-a`) FROM table```. +- You cannot use column alias in formulas. E.g. `SELECT abs(t.column) FROM table AS t;` is invalid. +- Formulas can be used in `GROUP BY` and `ORDER BY` clauses. + +For an exhaustive list of available functions, please refer to the complete [function reference](./functions.md). + +### Extended list operators + +Some column types in SeaTable have list values. The SeaTable UI supports a few special filters for such types, which are `HAS ANY OF`, `HAS ALL OF`, `HAS NONE OF` and `IS EXACTLY`. You can use the same syntax to filter such columns with SQL. For all these operators, the list of string constant are enclosed with brackets, just like the syntax for `IN`. Please note that the order of values in the list is not taken into account. + +__Example__ `SELECT * FROM table WHERE city HAS ANY OF ("New York", "Paris")` will retrieve all rows that contain either "New York" or "Paris" in the "multiple select"-type column `city` + +## Big Data storage indexes + +To improve query performance, SeaTable will automatically create indexes for the rows stored in big data storage engine. Currently, text, number, date, single select, multiple select, collaborators, creator, create date, modifier and modification date columns are indexed. + +When you add or delete a column in a table, the index for this column is not added/removed immediately. Indexes creation and deletion are triggered in two cases: + +1. When you archive the table for the next time, indexes are created for new columns and indexes for removed columns are removed. +2. Users may manage indexes from "index management" UI. You can open it from the "Big data management" menu in the base. diff --git a/docs/scripts/sql/reference.md b/docs/scripts/sql/reference.md deleted file mode 100644 index 6258c5ef..00000000 --- a/docs/scripts/sql/reference.md +++ /dev/null @@ -1,192 +0,0 @@ -# SQL in SeaTable - -You can use SQL to query data in SeaTable. If some tables in a base are archived, archived rows are also queried, as well as rows that are not archived yet. - -## Supported SQL Syntax - -Currently only `select`, `insert`, `update`, and `delete` statements are supported. (`insert`, `update`, and `delete` statements require version 2.7 or later) - -The syntax of `select` statement is: - -``` -SELECT [DISTINCT] fields FROM table_name [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [Limit Option] -``` - -Since version 4.3, basic JOIN query is supported, for example: - -``` -SELECT ... FROM table1, table2 WHERE table1.column1 = table2.column2 AND ... -``` - -The JOIN query has the following restrictions: - -- Only `INNER JOIN` is supported, `LEFT JOIN`, `RIGHT JOIN`, and `FULL JOIN` are not supported. -- Tables in the `FROM` clause should be unique (no duplicate tables). -- Each table in the `FROM` clause should be associated with at least one join condition. -- Join conditions should be placed in the `WHERE` clause, and connected with `AND`s. -- Join conditions can only use equality operator on columns, e.g. `table1.column1 = table2.column2`. -- Columns in join conditions must be indexed, unless the table is not archived. - -Notes: - -- Most SQL syntax can be used in where clause, including arithmetic expressions, comparison operators, `[NOT] LIKE`, `IN`, `BETWEEN ... AND ...`, `AND`, `OR`, `NOT`, `IS [NOT] TRUE`, `IS [NOT] NULL`. - - Arithmetic expressions only support numbers. - - `LIKE` only supports strings. The key word `ILIKE` can be used instead of `LIKE` to make the match case-insensitive. - - `BETWEEN ... AND ...` only supports numbers and time. - - Time constants should be strings in ISO format (e.g. "2020-09-08 00:11:23"). Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). -- `GROUP BY` uses strict syntax. The selected fields must appear in group by list, except for aggregation functions (`COUNT`, `SUM`, `MAX`, `MIN`, `AVG`) and formulas (see extended syntax section below). -- `HAVING` filters rows resulting from the group by clause. Only fields referred in the "GROUP BY" clause or aggregation functions (such as "SUM") can be used in having clause. Other syntax is the same as specified for the where clause. -- Fields in "order by" list must be a column or an expression in the selected fields. For example, `select a from table order by b` is invalid; while `select a from table order by b` and `select abs(a), b from table order by abs(a)` are valid. -- Limit options are in MySQL format. The general syntax is `LIMIT ... OFFSET ...`. You may omit `LIMIT` or `OFFSET`. -- Field alias with `AS` syntax is supported. For example, `select table.a as a from table` returns rows whose first column is keyed by "a". There are two notes: - - Field alias can be referred in `group by`, `having` and `order by` clauses. E.g., `select t.registration as r, count(*) as c from t group by r having c > 100` is valid. - - Field alias cannot be referred in `where` clause. E.g., `select t.registration as r, count(*) from t group by r where r > "2020-01-01"` will report syntax error. - -Each returned row is a JSON map. The keys of the maps are the column keys, NOT column names. To use column names as keys, the `convert_keys` parameter (available since version 2.4) in query request should be TRUE. For JOIN query, the keys of row maps match the "id" fields (not the "key" or the "name"). Those column fields (e.g. id, key, name) are returned under `metadata` array in query response. - -The syntax of `insert`, `update`, and `delete` statements are: - -``` -INSERT INTO table_name [column_list] VALUES value_list [, ...] - -UPDATE table_name SET column_name = value [, ...] [WhereClause] - -DELETE FROM table_name [WhereClause] - -``` - -- `column_list` is a list of column names surrounded by parentheses. If omitted, it defaults to all updatable columns. -- `value_list` is a list of values surrounded by parentheses. Values must be in the same order as the column list, for example: `(1, "2", 3.0)`. -- Multivalued columns, such as multiple-select column type, requires values to be surrounded by parentheses, for example: `(1, "2", 3.0, ("foo", "bar"))`. -- Values of single-select and multiple-select column types must be option names, not option keys. -- `WhereClause` is optional. If omitted, all rows in the table are included. -- `INSERT` statement only supports bases that have been archived. The rows will be inserted into big-data storage. It'll return error if the base is not archived yet. If you want to insert rows into such bases, please use API for adding rows (e.g. [Python API](../python/objects/rows.md)). -- `UPDATE` and `DELETE` statements allows updating/deleting rows in both normal and big-data storage. - -Note: these column types are _not_ allowed to insert or update: - -- built-in columns, such as `_id`, `_ctime`. -- image, file, formula, link, link-formula, geolocation, auto-number, button - -## Data Types - -Below is mapping from SeaTable column types to SQL column types. - -| SeaTable Column Type | SQL Column Type | Query result format | Use in WHERE clause | Use in GROUP BY / ORDER BY clause | -| :-------------------- | :--------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------- | -| text | String | | Supported | Supported. | -| long-text | String | Raw text in Markdown format | Supported | Supported | -| number | Float | | Supported | Supported | -| single-select | String | Returned rows contain the option key by default. To return the option name, the `convert_keys` parameter (available since version 2.4) in query request should be TRUE. | Refer an option by its name. E.g. `where single_select = "New York"`. | Order by the definition order of the options | -| multiple-select | List of strings | Returned rows contain the option key by default. To return the option name, the `convert_keys` parameter (available since version 2.4) in query request should be TRUE. | Refer an option by its name. E.g. `where multi_select = "New York"`. More details in "List Types" section below. | More details in "List Types" section below. | -| checkbox | Bool | | Supported | Supported | -| date | Datetime | Time strings in RFC 3339 format | Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| image | List of URL for images | A JSON array with image URLs as elements | Supported. More details in "List Types" section below. | Supported. More details in "List Types" section below. | -| file | Will be returned as JSON format string when queried. | Not supported | Not Supported | Not Supported | -| collaborator | List of user IDs | Format is like 5758ecdce3e741ad81293a304b6d3388@auth.local. If you need user names, you have to convert with Seatable APIs. | Supported. More details in "List Types" section below. | Supported. More details in "List Types" section below. | -| link to other records | List of linked rows | Supported. More details in "List Types" section below. | Supported. More details in "List Types" section below. | Supported. More details in "List Types" section below. | -| formula | The type depends on the return value of the formula. | Depends on the type of the return value | Depends on the type of the return value | Depends on the type of the return value | -| \_creator | User ID as string | Format is like 5758ecdce3e741ad81293a304b6d3388@auth.local. If you need user names, you have to convert with Seatable APIs. | Supported | Supported | -| \_ctime | Datetime | Time strings in RFC 3339 format | Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| \_last_modifier | User ID as string | Format is like 5758ecdce3e741ad81293a304b6d3388@auth.local. If you need user names, you have to convert with Seatable APIs. | Supported | Supported | -| \_mtime | Datetime | Time strings in RFC 3339 format | Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| auto number | String | | Supported | Supported. | -| url | String | | Supported | Supported. | -| email | String | | Supported | Supported. | -| duration | Float | Returned in the unit of seconds | Supported | Supported. | - -### List Types - -In SeaTable, two categories of column types are list types: - -- Built-in list types: including multiple selection, image, collaborator, and link to other records. -- Return values for the following link formulas: formula columns whose formula is `{link.column}` or `lookup`; link formula columns whose formula is `lookup`, `findmin` or `findmax`. - -When referring a column with list type in `where` conditions, the following rules apply, depending on the type for the list elements. (If an operator is not listed below, it's unsupported.) - -| Element Type | Operator | Rule | -| :------------ | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | -| string | IN, extended list operators (e.g. `has any of`) | Follow the rules of the operator. | -| string | LIKE, ILIKE | Always take the first element for comparison; if there is no element, use "". - | -| string | IS NULL | Return `true` when the list is empty or no data in the cell. | -| string | =, != | Always take the first element for comparison; if there is no element, use "". | -| float | IN, extended list operators (e.g. `has any of`) | Follow the rules of the operator. | -| float | =, !=, \<, \<=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | -| float | IS NULL | Return `true` when the list is empty or no data in the cell. | -| float | Arithmetics such as \+/-/\*// | Use the first element for calculation. | -| Datetime | IN, extended list operators (e.g. `has any of`) | Follow the rules of the operator. | -| Datetime | =, !=, \<, \<=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | -| Datetime | IS NULL | Return `true` when the list is empty or no data in the cell. | -| bool | IS TRUE | Always take the first element for comparison; return false if there are no elements. - | -| linked record | | Follow the rules for the type of the display column. | - -When a list column is returned in a selected field, only the first 10 elements are returned. - -When used in `group by` or `order by` clauses, the elements for each list will first be sorted in ascending order, then the lists will be sorted by the rules below: - -- Compare the elements one by one, list with smaller element is sorted before list with larger element. -- If all elements compared in step 1 are equal, shorter list is sorted before longer list. -- Otherwise the tow lists are equal. - -If a list column is passed as parameter to a formula, and the parameter expects a scalar value, the first element will be used. And if the element is a linked record, the value of its display column will be used. - -When applying aggregate functions (min, max, sum, avg) to a list column, if there is only 1 element in the list, use that element; otherwise this row will not be aggregated. - -### NULL Values - -NULL value is distinct from 0. It represents a missing value. The following values are treated as NULL: - -- Empty cells in a table is treated as NULL. -- Values which cannot be converted to the column type will be treated as NULL. -- Empty strings ("") will be treated as NULL too. This is different from standard SQL. -- Lists are treated as NULL based on the rules described in the "List Types" section. -- Functions or formula columns that return error will be treated as NULL. - -In the `Where` clause: - -- Arithmetic operations (+, -, \* etc.) on NULL values will return NULL. -- `!=`, `NOT LIKE`, `NOT IN`, `NOT BETWEEN`, `HAS NONE OF`, `IS NOT TRUE`, and `IS NULL` operations will return true when the value is NULL. -- `AND`, `OR`, `NOT` treat NULL values as false. -- Aggregate functions (min, max, sum, avg) will ignore NULL values. - -In formulas, NULL values will be converted to 0 or an empty strings. - -## Extended Syntax - -### Use Formulas in SQL Query - -You may use a formula syntax that's almost the same as SeaTable's formulas in SQL queries. There are a few special notes: - -- Link formulas are not supported. e.g. {link.age} is invalid. -- Reference to columns should not be enclosed by curly brackets ("{}"). Don't write `select abs({column}) from table;`. Write `select abs(column) from table;`. This is consistent with standard SQL syntax. -- You can use back quote ("\`\`") to enclose column references, when column name contains space or "-". E.g. select abs(`column-a`) from table; -- You may not use column alias in formulas. E.g. `select abs(t.column) from table as t;` is invalid. -- formulas can be use in group by and order by clauses. - -A few extended formulas are supported: - -- `STARTOFWEEK(date, weekStart)`: returns the first day of the week where "date" is in. "weekstart" can be used to choose "sunday" or "monday" as the first day of a week. -- `Quarter(date)`: Returns the quarter of the date. Return value is 1, 2, 3 or 4. -- `ISODate(date)`: Returns ISO format string for the date. E.g. "2020-09-08". -- `ISOMonth(date)`: Returns ISO format string for the month where "date" is in. E.g. "07". - -The above formulas can be used for group by week, quarter, date and month. E.g. `select sum(sale) from SalesRecord group by ISODate(SalesTime);` will return the total sales amount for each day. - -For more details, please refer to \[./function.md]. - -### Extended List Operators - -Some column types in SeaTable have list values. The SeaTable UI supports a few special filters for such types. They are `HAS ANY OF`, `HAS ALL OF`, `HAS NONE OF`, `IS EXACTLY`. You may also use the same syntax to filter such columns with SQL. - -For example, if column "city" is of type multi-select, and we want to find out all rows that contains "New York" or "Paris" in the "city" column, you can query: `select * from table where city has any of ("New York", "Paris");`. The list of string constant are enclosed with brackets, just like the syntax for `IN`. - -## Indexes - -To improve query performance, SeaTable will automatically create indexes for the rows stored in big data storage engine. Currently, text, number, date, single-select, multiple-select, collaborators, creator, create date, modifier, modification date columns are indexed. - -When you add or delete a column in a table, the index for this column is not added/removed immediately. Indexes creation and deletion are triggered in two cases: - -1. When you archive the table for the next time, indexes are created for new columns and indexes for removed columns are removed. -2. Users may manage indexes from "index management" UI. You can open it from the "Big data management" menu in the base. diff --git a/mkdocs.yml b/mkdocs.yml index 7090b187..46ad963a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,7 +9,7 @@ repo_name: seatable/seatable-developer-docs repo_url: https://github.com/seatable/seatable-developer-docs/ # Copyright -copyright: Copyright © 2022 - 2023 SeaTable GmbH +copyright: Copyright © 2022 - 2025 SeaTable GmbH # Configuration theme: @@ -54,6 +54,8 @@ plugins: - minify: minify_html: true - git-revision-date-localized + - include-markdown: + rewrite_relative_urls: false #- redirects: # redirect_maps: # 'changelog/server-changelog.md': 'https://seatable.com/changelog/' @@ -113,13 +115,11 @@ markdown_extensions: nav: - Introduction: - index.md - - Requirements: introduction/requirements.md - - Basic concepts: introduction/basic_concepts.md - - Get Support: introduction/get_support.md + - Coding for beginners: introduction/coding_for_beginners.md + - Get support: introduction/get_support.md - Scripting in SeaTable: - scripts/index.md - JavaScript: - - Basic structure: scripts/javascript/basic_structure_js.md - Objects & Methods: - scripts/javascript/objects/index.md - Tables: scripts/javascript/objects/tables.md @@ -133,14 +133,15 @@ nav: - Examples: - scripts/javascript/examples/index.md - 1. Add rows: scripts/javascript/examples/auto-add-rows.md - - 2. Get incremental: scripts/javascript/examples/get-incremental.md - - 3. Create statistics: scripts/javascript/examples/statistics-attendance.md + - 2. Calculate accumulated value: scripts/javascript/examples/calculate-accumulated-value.md + - 3. Compute attendance statistics: scripts/javascript/examples/compute-attendance-statistics.md - Common questions: scripts/javascript/common_questions.md - Python: - - Basic structure: scripts/python/basic_structure_python.md + - Introduction: scripts/python/introduction.md - Objects & Methods: - scripts/python/objects/index.md - Metadata: scripts/python/objects/metadata.md + - Context: scripts/python/objects/context.md - Tables: scripts/python/objects/tables.md - Views: scripts/python/objects/views.md - Columns: scripts/python/objects/columns.md @@ -150,32 +151,31 @@ nav: - Big data: scripts/python/objects/big_data.md - Accounts: scripts/python/objects/accounts.md - Users: scripts/python/objects/users.md - - Date Utilities: scripts/python/objects/date-utils.md - - Context: scripts/python/objects/context.md - - Constants: scripts/python/objects/constants.md - - Notifications: scripts/python/objects/notifications.md - - Websockets: scripts/python/objects/websockets.md - - Authorization: scripts/python/authorization_python.md + - Date utilities: scripts/python/objects/date-utils.md + - Communication utilities: scripts/python/objects/communication-utils.md - Examples: - scripts/python/examples/index.md - - 1. Send E-mails: scripts/python/examples/send_email.md - - 2. Generate Barcode: scripts/python/examples/generate_barcode.md - - 3. Generate QR-Code: scripts/python/examples/generate_qrcode.md - - 4. Sync MySQL: scripts/python/examples/sync_mysql.md - - 5. Watch Stock Price: scripts/python/examples/update_stock_price.md - - 6. Merge PDF: scripts/python/examples/merge_pdf.md - - 7. Convert HEIC to PNG: scripts/python/examples/heic_to_png.md + - 1. Add rows: scripts/python/examples/auto-add-rows.md + - 2. Calculate accumulated value: scripts/python/examples/calculate-accumulated-value.md + - 3. Compute attendance statistics: scripts/python/examples/compute-attendance-statistics.md + - 4. Send emails: scripts/python/examples/send_email.md + - 5. Generate barcode: scripts/python/examples/generate_barcode.md + - 6. Generate QR code: scripts/python/examples/generate_qrcode.md + - 7. Sync MySQL: scripts/python/examples/sync_mysql.md + - 8. Watch stock price: scripts/python/examples/update_stock_price.md + - 9. Merge PDF: scripts/python/examples/merge_pdf.md + - 10. Convert HEIC to PNG: scripts/python/examples/heic_to_png.md - Common questions: scripts/python/common_questions.md - Query with SQL: - - SQL Reference: scripts/sql/reference.md - - SQL Function Reference: scripts/sql/functions.md + - Introduction: scripts/sql/introduction.md + - SQL function reference: scripts/sql/functions.md - Plugin Development: - plugins/index.md - Environments: plugins/environments.md - Available methods: plugins/methods.md - Client APIs: - clients/index.md - - Javascript: + - JavaScript: - Introduction: clients/javascript/javascript_api.md - Objects & Methods: - Metadata: clients/javascript/metadata.md diff --git a/preview.sh b/preview.sh index ca035f72..ffb7aff7 100755 --- a/preview.sh +++ b/preview.sh @@ -3,12 +3,12 @@ # builds the mkdocs image and runs it to provide a local preview of the docs if [[ "$1" == "-stop" ]]; then - sudo docker kill seatable-admin-docs + sudo docker kill seatable-developer-docs exit 0 fi -sudo docker build -t seatable-admin-docs . -sudo docker run --name seatable-admin-docs --rm -d -p 8000:8000 -v ${PWD}:/docs seatable-admin-docs +sudo docker build -t seatable-developer-docs . +sudo docker run --name seatable-developer-docs --rm -d -p 8000:8000 -v ${PWD}:/docs seatable-developer-docs echo "Local documentation preview available at http://127.0.0.1:8000" echo "Use './preview.sh -stop' to stop the preview" diff --git a/requirements.txt b/requirements.txt index c92cc3e1..69e108a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ mkdocs-material-extensions mkdocs-redirects mkdocs-git-revision-date-localized-plugin mkdocs-minify-plugin +mkdocs-include-markdown-plugin # Fix for broken file watching (https://github.com/mkdocs/mkdocs/issues/4032) click==8.2.1