- create_deliverables_fields.py: one-time schema setup (56 fields) - deliverables_sync.py: manual JSON-to-Airtable sync tool - deliverables_service.py: production daemon with watchdog file watching, batch upsert, processed/failed file handling, and daily HTML email reports - loreal-deliverables.service: systemd unit for server deployment - Server: box-cli-01 at /home/dalim/LOREAL-AIRTABLE/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
131 lines
4.8 KiB
Python
131 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Create fields in the L'Oreal Deliverables Airtable table.
|
|
Run once to set up the table schema before using deliverables_sync.py.
|
|
"""
|
|
|
|
from pyairtable import Api
|
|
import time
|
|
import sys
|
|
|
|
# Configuration
|
|
PAT_TOKEN = "pat32GcLDAyyT4V45.928c9f011206e1b8d26608436f8dcef6feb3eabcfe59a27e70459ac6dff54dde"
|
|
BASE_ID = "apptPeKvHf7wZqjc5"
|
|
TABLE_ID = "tbldCaDgXF9ewNjPq"
|
|
|
|
# Field definitions: (name, type, options_or_None)
|
|
FIELDS = [
|
|
# --- Upsert Key ---
|
|
("Number", "singleLineText", None),
|
|
|
|
# --- JobDetails ---
|
|
("Job_BusinessArea", "singleLineText", None),
|
|
("Job_Company", "singleLineText", None),
|
|
("Job_Campaign", "singleLineText", None),
|
|
("Job_Title", "singleLineText", None),
|
|
("Job_Notes", "multilineText", None),
|
|
("Job_ClientCode", "singleLineText", None),
|
|
("Job_StudioCode", "singleLineText", None),
|
|
("Job_CampaignCode", "singleLineText", None),
|
|
("Job_DueDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Job_BriefDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Job_LiveDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Job_ExternalReference", "singleLineText", None),
|
|
("Job_Type", "singleLineText", None),
|
|
("Job_JobCategory", "singleLineText", None),
|
|
("Job_MediaType", "singleLineText", None),
|
|
("Job_MediaSubType", "singleLineText", None),
|
|
("Job_LanguageName", "singleLineText", None),
|
|
("Job_LanguageCode", "singleLineText", None),
|
|
("Job_CountryName", "singleLineText", None),
|
|
("Job_CountryCode", "singleLineText", None),
|
|
("Job_AutomationWorkflow", "singleLineText", None),
|
|
("Job_Outputs", "multilineText", None),
|
|
|
|
# --- Activation Data (from JobDetails.activation_data) ---
|
|
("Activation_Status", "singleLineText", None),
|
|
("Activation_Destination", "singleLineText", None),
|
|
("Activation_Format", "singleLineText", None),
|
|
("Activation_MasterJob", "singleLineText", None),
|
|
("Activation_CreativeExecution", "singleLineText", None),
|
|
("Activation_PimAssetId", "singleLineText", None),
|
|
("Activation_PimImageUrl", "singleLineText", None),
|
|
("Activation_MasterAsset", "singleLineText", None),
|
|
("Activation_MasterAutomation", "singleLineText", None),
|
|
("Activation_Submedia", "singleLineText", None),
|
|
("Activation_Jobcat", "singleLineText", None),
|
|
("Activation_Country", "singleLineText", None),
|
|
("Activation_Language", "singleLineText", None),
|
|
("Activation_MediaType", "singleLineText", None),
|
|
("Activation_Qty", "singleLineText", None),
|
|
("Activation_LiveDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Activation_ReadyForProductionDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
|
|
# --- ProjectDetails ---
|
|
("Project_BusinessArea", "singleLineText", None),
|
|
("Project_Title", "singleLineText", None),
|
|
("Project_ExternalReference", "singleLineText", None),
|
|
("Project_Type", "singleLineText", None),
|
|
("Project_Description", "multilineText", None),
|
|
("Project_StartDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Project_EndDate", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Project_AdditionalAttributes", "multilineText", None),
|
|
|
|
# --- AssetDetails ---
|
|
("Asset_Filename", "singleLineText", None),
|
|
("Asset_Title", "singleLineText", None),
|
|
("Asset_Description", "singleLineText", None),
|
|
("Asset_Type", "singleLineText", None),
|
|
("Asset_Version", "number", {"precision": 0}),
|
|
("Asset_Uploaded", "date", {"dateFormat": {"name": "iso"}}),
|
|
("Asset_Status", "singleLineText", None),
|
|
("Asset_Details", "multilineText", None),
|
|
]
|
|
|
|
|
|
def main():
|
|
dry_run = "--dry-run" in sys.argv
|
|
|
|
if dry_run:
|
|
print("DRY RUN - No fields will be created\n")
|
|
|
|
print(f"Target: Base {BASE_ID} / Table {TABLE_ID}")
|
|
print(f"Fields to create: {len(FIELDS)}\n")
|
|
|
|
api = Api(PAT_TOKEN)
|
|
table = api.table(BASE_ID, TABLE_ID)
|
|
|
|
created = 0
|
|
skipped = 0
|
|
failed = 0
|
|
|
|
for i, (name, field_type, options) in enumerate(FIELDS, 1):
|
|
label = f"[{i}/{len(FIELDS)}] {name} ({field_type})"
|
|
|
|
if dry_run:
|
|
print(f" Would create: {label}")
|
|
continue
|
|
|
|
try:
|
|
kwargs = {"name": name, "field_type": field_type}
|
|
if options:
|
|
kwargs["options"] = options
|
|
result = table.create_field(**kwargs)
|
|
print(f" Created: {label} -> {result.id}")
|
|
created += 1
|
|
except Exception as e:
|
|
err = str(e)
|
|
if "already exists" in err.lower() or "duplicate" in err.lower():
|
|
print(f" Skipped (exists): {label}")
|
|
skipped += 1
|
|
else:
|
|
print(f" FAILED: {label} - {err}")
|
|
failed += 1
|
|
|
|
time.sleep(0.3)
|
|
|
|
print(f"\nDone! Created: {created}, Skipped: {skipped}, Failed: {failed}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|