Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/clients/python_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/includes.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ Here is the structure of the table named `Daily expenses` you need so that this
<!--accumulated-value-example-start-->
# 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:

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Binary file modified docs/media/SeaTable examples material (scripts included).dtable
Binary file not shown.
Binary file modified docs/media/SeaTable examples material (without scripts).dtable
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/scripts/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Scripting in SeaTable
# Scripting

## Supported scripting languages and requirements

Expand Down
4 changes: 2 additions & 2 deletions docs/scripts/javascript/common_questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) + "]");
}
Expand All @@ -52,7 +52,7 @@
} else {
output.text(indenterChar.repeat(indent) + key + ": ");
indent += 1;
jsonPrettyFormat(value,indent);
jsonPrettyFormat(value, indent);
}
}
indent -= 1;
Expand Down
4 changes: 2 additions & 2 deletions docs/scripts/javascript/objects/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)});
```
Expand Down Expand Up @@ -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');
```
Expand Down
2 changes: 1 addition & 1 deletion docs/scripts/javascript/objects/rows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
8 changes: 5 additions & 3 deletions docs/scripts/python/common_questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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)!
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions docs/scripts/python/examples/calculate-accumulated-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```
2 changes: 1 addition & 1 deletion docs/scripts/python/examples/generate_barcode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion docs/scripts/python/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down
58 changes: 33 additions & 25 deletions docs/scripts/python/examples/send_email.md
Original file line number Diff line number Diff line change
@@ -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:

Expand All @@ -14,24 +14,26 @@ 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

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.

## Code

```python linenums="1"
import markdown
import smtplib, ssl
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
Expand All @@ -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')
Expand All @@ -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)

Expand All @@ -117,7 +119,13 @@ elif EMAIL_FORMAT == "html" :
</body>
</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
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions docs/scripts/python/objects/date-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/scripts/python/objects/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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)"
Expand Down
Loading