diff --git a/docs/clients/python_api.md b/docs/clients/python_api.md index 68c223e..a2d1523 100644 --- a/docs/clients/python_api.md +++ b/docs/clients/python_api.md @@ -3,7 +3,7 @@ The SeaTable Python Client encapsulates SeaTable Server Restful API. You can call it in your python program. !!! 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." + 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/includes.md b/docs/includes.md index a79c10e..1d1ddc7 100644 --- a/docs/includes.md +++ b/docs/includes.md @@ -476,7 +476,7 @@ Here is the structure of the table named `Daily expenses` you need so that this # 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**. +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: diff --git a/docs/index.md b/docs/index.md index b63820d..6394ef5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,9 +55,9 @@ 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. + 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. + Python scripts require an authentication to get data from the base, but the `context` objects contains everything for an easy authentication. === "Plugins" diff --git a/docs/media/SeaTable examples material (scripts included).dtable b/docs/media/SeaTable examples material (scripts included).dtable index 959633e..24bef74 100644 Binary files a/docs/media/SeaTable examples material (scripts included).dtable and b/docs/media/SeaTable examples material (scripts included).dtable differ diff --git a/docs/media/SeaTable examples material (without scripts).dtable b/docs/media/SeaTable examples material (without scripts).dtable index 5e05d46..545faf5 100644 Binary files a/docs/media/SeaTable examples material (without scripts).dtable and b/docs/media/SeaTable examples material (without scripts).dtable differ diff --git a/docs/scripts/index.md b/docs/scripts/index.md index 78f4b40..1440bbf 100644 --- a/docs/scripts/index.md +++ b/docs/scripts/index.md @@ -1,4 +1,4 @@ -# Scripting in SeaTable +# Scripting ## Supported scripting languages and requirements diff --git a/docs/scripts/javascript/common_questions.md b/docs/scripts/javascript/common_questions.md index 4487880..0e6caaa 100644 --- a/docs/scripts/javascript/common_questions.md +++ b/docs/scripts/javascript/common_questions.md @@ -36,7 +36,7 @@ if (json instanceof Array) { output.text(indenterChar.repeat(indent) + "["); indent += 1; - json.forEach((elem)=>jsonPrettyFormat(elem,indent)); + json.forEach((elem)=>jsonPrettyFormat(elem, indent)); indent -= 1; output.text(indenterChar.repeat(indent) + "]"); } @@ -52,7 +52,7 @@ } else { output.text(indenterChar.repeat(indent) + key + ": "); indent += 1; - jsonPrettyFormat(value,indent); + jsonPrettyFormat(value, indent); } } indent -= 1; diff --git a/docs/scripts/javascript/objects/links.md b/docs/scripts/javascript/objects/links.md index 4e1b1c8..fd2afff 100644 --- a/docs/scripts/javascript/objects/links.md +++ b/docs/scripts/javascript/objects/links.md @@ -139,7 +139,7 @@ ```js const table = base.getTableByName('Table1'); - const linkColumn = base.getColumnByName(table,'Table2 link'); + 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)}); ``` @@ -175,7 +175,7 @@ const table1LinkColumnName = "Table2 link"; const table2Name = "Table2"; - const linId = base.getColumnLinkId(table1Name,table1LinkColumnName); /* (1)! */ + const linId = base.getColumnLinkId(table1Name, table1LinkColumnName); /* (1)! */ const currentRowId = base.context.currentRow._id; base.addLink(linId, table1Name, table2Name, currentRowId, 'J5St2clyTMu_OFf9WD8PbA'); ``` diff --git a/docs/scripts/javascript/objects/rows.md b/docs/scripts/javascript/objects/rows.md index c5e127f..9f4cef7 100644 --- a/docs/scripts/javascript/objects/rows.md +++ b/docs/scripts/javascript/objects/rows.md @@ -649,7 +649,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa | 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 | + | String | Text, Long Text, URL, Email, Single Select | Unsupported | String | Unsupported | | List | Multiple Select | Unsupported | String | Unsupported | | 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 | diff --git a/docs/scripts/python/common_questions.md b/docs/scripts/python/common_questions.md index faeedb1..54de2da 100644 --- a/docs/scripts/python/common_questions.md +++ b/docs/scripts/python/common_questions.md @@ -67,6 +67,8 @@ - [scipy](https://pypi.org/project/scipy/): Fundamental algorithms for scientific computing in Python - [PyPDF](https://pypi.org/project/pypdf/): PDF toolkit for Python - [pdfmerge](https://pypi.org/project/pdfmerge/): Merge PDF files + - [Markdown](https://pypi.org/project/Markdown/): Convert Markdown to HTML + - [RapidFuzz](https://pypi.org/project/RapidFuzz/): A fast string matching library using string similarity calculations This list is not exhaustive. For a complete, up-to-date list of available third-party packages, you can run the following Python script in your SeaTable environment: @@ -96,8 +98,8 @@ ```python import json # (1)! - from seatable_api import Base,context - base = Base(context.api_token,context.server_url) + from seatable_api import Base, context + base = Base(context.api_token, context.server_url) base.auth() print(json.dumps(base.get_metadata(), indent=' ')) # (2)! @@ -125,7 +127,7 @@ # 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)) + 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) diff --git a/docs/scripts/python/examples/calculate-accumulated-value.md b/docs/scripts/python/examples/calculate-accumulated-value.md index 96118b0..2ac20a0 100644 --- a/docs/scripts/python/examples/calculate-accumulated-value.md +++ b/docs/scripts/python/examples/calculate-accumulated-value.md @@ -40,7 +40,7 @@ if 'groupbys' in view and len(view['groupbys']) > 0 : 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) : + for row_index, row in enumerate(group_rows) : current_number = row[value_column_name]; if current_number : # Calculate increment @@ -52,7 +52,7 @@ if 'groupbys' in view and len(view['groupbys']) > 0 : base.update_row(table_name, row['_id'], {incremental_column_name: increase_count}) else : incremental_total = 0 - for row_index,row in enumerate(rows) : + for row_index, row in enumerate(rows) : current_number = row[value_column_name]; if current_number : # Calculate increment diff --git a/docs/scripts/python/examples/compute-attendance-statistics.md b/docs/scripts/python/examples/compute-attendance-statistics.md index 7f4dc8f..c3d2007 100644 --- a/docs/scripts/python/examples/compute-attendance-statistics.md +++ b/docs/scripts/python/examples/compute-attendance-statistics.md @@ -79,5 +79,5 @@ for date_key in grouped_rows : 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) +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 92e5fb7..41b2e64 100644 --- a/docs/scripts/python/examples/generate_barcode.md +++ b/docs/scripts/python/examples/generate_barcode.md @@ -43,7 +43,7 @@ CUSTOM_OPTIONS = { "module_width": 0.2, # width of single stripe 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 + "font_size": 10, # font size of the text below the barcode, pt "text_distance": 5.0, # distance between the text and the barcode, mm } diff --git a/docs/scripts/python/examples/index.md b/docs/scripts/python/examples/index.md index 1bdd017..116efe1 100644 --- a/docs/scripts/python/examples/index.md +++ b/docs/scripts/python/examples/index.md @@ -30,7 +30,7 @@ This script computes, from a list of clocking times, daily clock in (earliest cl ## 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. +This Python script demonstrates sending emails via SMTP using the smtplib module, constructing MIME objects to compose rich content emails within SeaTable and creating HTML content from a "long text"-type column using the markdown module. [read more :material-arrow-right-thin:](/scripts/python/examples/send_email/) diff --git a/docs/scripts/python/examples/send_email.md b/docs/scripts/python/examples/send_email.md index 6aef30f..68a1b7a 100644 --- a/docs/scripts/python/examples/send_email.md +++ b/docs/scripts/python/examples/send_email.md @@ -1,6 +1,6 @@ # Send emails -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: +This Python script demonstrates sending emails via SMTP using the [smtplib module](https://docs.python.org/3/library/smtplib.html), constructing MIME objects to compose rich content emails within SeaTable and creating HTML content from a "long text"-type column using the markdown module. It also retrieves configuration parameters from the database. This example uses two tables: - The `Contacts` table storing the contacts you want to send email to: @@ -14,10 +14,10 @@ This Python script demonstrates sending emails via SMTP using the [smtplib modul | ----------- |: ----- :|: ------------- :|: ----------- :|: ---------- :|: --------- :|: -- :| | **Column type** | text | single select | single select | single select | checkbox | file | -- 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). +- Recipient email can be `hard-coded` (recipients are defined l.48 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.57 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.71), `html` (HTML-formatted message, defined l.77) or `database` (the email body retrieved from the `Email body` column of the `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.115 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). ## Process overview @@ -25,6 +25,7 @@ This Python script demonstrates sending emails via SMTP using the [smtplib modul 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**. +3. Eventually **retrieves email body**. 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. @@ -32,6 +33,7 @@ This Python script demonstrates sending emails via SMTP using the [smtplib modul ## Code ```python linenums="1" +import markdown import smtplib, ssl from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart @@ -54,23 +56,23 @@ CONFIG_TABLE = 'Send email config' CONTACTS_TABLE = 'Contacts' # SMTP server configurations for sending emails -smtp_server = 'my.smtpserver.com' -smtp_port = 465 -username = 'my.em@il.com' -password = 'topsecret' -sender = 'My name' +SMTP_SERVER = 'my.smtpserver.com' +SMTP_PORT = 465 +USERNAME = 'my.em@il.com' +PASSWORD = 'topsecret' +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) +# Choose RECIPIENT_EMAIL between "hard-coded" (addresses l.48 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) +# Choose SUBJECT between "hard-coded" (subject l.57 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) +# Choose EMAIL_FORMAT between "text" (hard-coded plain text, defined l.71), +# "html" (hard-coded HTML, defined l.77) +# and "database" (content of the 'Email body' 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') @@ -96,12 +98,12 @@ elif SUBJECT_SOURCE == "database" : # 4. Construct the email message msg = MIMEMultipart() msg['Subject'] = subject -msg['From'] = sender + '<' + username + '>' +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 = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.seatable.com" text_plain = MIMEText(text,'plain', 'utf-8') msg.attach(text_plain) @@ -117,7 +119,13 @@ elif EMAIL_FORMAT == "html" : """ - text_html = MIMEText(html,'html', 'utf-8') + text_html = MIMEText(html, 'html', 'utf-8') + msg.attach(text_html) + +elif EMAIL_FORMAT == "database" : + # Option c) HTML content for the email body from the Email body column + current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] + text_html = MIMEText(markdown.markdown(current_row['Email body']),'html', 'utf-8') msg.attach(text_html) # 5. Attach a file from SeaTable to the email @@ -128,7 +136,7 @@ if ATTACH_FILE : 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: @@ -148,8 +156,8 @@ if ATTACH_FILE : # 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.connect(SMTP_SERVER) + email_server.login(USERNAME, PASSWORD) email_server.send_message(msg) email_server.quit() except smtplib.SMTPAuthenticationError: @@ -161,9 +169,9 @@ except Exception as e: # option b) Sending the email using SMTP / SSL ssl_context = ssl.create_default_context() try: - with smtplib.SMTP_SSL(smtp_server, smtp_port, + with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=ssl_context) as email_server: - email_server.login(username, password) + email_server.login(USERNAME, PASSWORD) email_server.send_message(msg) email_server.quit() except smtplib.SMTPAuthenticationError: @@ -173,9 +181,9 @@ except Exception as e: # option c) Sending the email using SMTP with STARTTLS try: - with smtplib.SMTP(smtp_server, smtp_port) as email_server: + with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as email_server: email_server.starttls() - email_server.login(username, password) + email_server.login(USERNAME, PASSWORD) email_server.send_message(msg) email_server.quit() except smtplib.SMTPAuthenticationError: diff --git a/docs/scripts/python/objects/date-utils.md b/docs/scripts/python/objects/date-utils.md index 74b9a4d..7b7fe2e 100644 --- a/docs/scripts/python/objects/date-utils.md +++ b/docs/scripts/python/objects/date-utils.md @@ -309,7 +309,7 @@ time_date = dateutils.date(time_year, time_month, time_day) # 2025-07-17 !!! abstract "now" - Return the ISO formatted current date and time,accurate to seconds. + Return the ISO formatted current date and time, accurate to seconds. ``` python dateutils.now() @@ -376,7 +376,7 @@ time_date = dateutils.date(time_year, time_month, time_day) # 2025-07-17 ``` python from seatable_api.date_utils import dateutils - dateutils.weekday("2025-6-2") # 0 (June 2,2025 was a Monday) + dateutils.weekday("2025-6-2") # 0 (June 2, 2025 was a Monday) ``` ### isoweekday diff --git a/docs/scripts/python/objects/files.md b/docs/scripts/python/objects/files.md index 7c1bbb6..ef5492b 100644 --- a/docs/scripts/python/objects/files.md +++ b/docs/scripts/python/objects/files.md @@ -169,7 +169,7 @@ Please note that uploading a file *to a cell* will require two or three steps, d local_file = '/Users/Markus/Downloads/seatable-logo.png' with open (local_file, 'rb') as f: content = f.read() - info_dict = base.upload_bytes_file = ('seatable-logo.png', content, file_type='image') + info_dict = base.upload_bytes_file('seatable-logo.png', content, file_type='image') ``` __Example: Upload a file from a website__ @@ -179,7 +179,7 @@ Please note that uploading a file *to a cell* will require two or three steps, d file_url = 'https://seatable.io/wp-content/uploads/2021/09/seatable-logo.png' response = requests.get(file_url) if response.status_code in range(200,300) : - info_dict = base.upload_bytes_file = ('seatable-logo.png', response.content) + info_dict = base.upload_bytes_file('seatable-logo.png', response.content) ``` !!! abstract "Upload (detailed two-steps method)" diff --git a/docs/scripts/python/objects/links.md b/docs/scripts/python/objects/links.md index 36b9251..70c3a8a 100644 --- a/docs/scripts/python/objects/links.md +++ b/docs/scripts/python/objects/links.md @@ -172,7 +172,7 @@ base = Base(context.api_token, context.server_url) base.auth() - lin_id = base.get_column_link_id(TABLE1_NAME,TABLE1_LINK_COLUMN_NAME); # (1)! + 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') ``` @@ -212,7 +212,7 @@ !!! abstract "batch_update_links" - 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). + 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) # (1)! diff --git a/mkdocs.yml b/mkdocs.yml index 46ad963..2d8efeb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -117,7 +117,7 @@ nav: - index.md - Coding for beginners: introduction/coding_for_beginners.md - Get support: introduction/get_support.md - - Scripting in SeaTable: + - Scripting: - scripts/index.md - JavaScript: - Objects & Methods: