""" create_adobe_template.py ------------------------ Creates "Paul Adobe Template" in Adobe Sign by: 1. Using the exact field positions from the downloaded David Tag Demo Form 2. Adding 4 extra fields (Number, Email, Company, Title) in available gaps Usage: python3 src/create_adobe_template.py """ import json import os import sys from dotenv import load_dotenv load_dotenv() sys.path.insert(0, os.path.dirname(__file__)) from adobe_api import adobe_api_post_multipart, adobe_api_post_json, adobe_api_put_json PDF_PATH = os.path.join( os.path.dirname(__file__), "..", "downloads", "David Tag Demo Form__CBJCHBCA", "Tag Demo Form_docx_pdf" ) FIELDS_JSON_PATH = os.path.join( os.path.dirname(__file__), "..", "downloads", "David Tag Demo Form__CBJCHBCA", "form_fields.json" ) TEMPLATE_NAME = "Paul Adobe Template" # Keys the Adobe Sign API accepts when writing form fields. # We strip server-generated metadata (origin, signerIndex, font/border styling). ALLOWED_KEYS = { "name", "inputType", "contentType", "validation", "validationData", "required", "readOnly", "locations", "assignee", "hiddenOptions", "visibleOptions", "defaultValue", "masked", "maskingText", "calculated", "urlOverridable", "minLength", "maxLength", "minValue", "maxValue", "validationErrMsg", "currency", "conditionalAction", "radioCheckType", } # Extra fields that cover types not present in the original David Tag form: # - NUMBER validation (TEXT_FIELD / DATA / NUMBER) # - SIGNER_EMAIL auto-fill # - COMPANY auto-fill # - TITLE auto-fill # # Placed in the two clear vertical gaps in the original layout: # Gap A: y=375–432 (between checkboxes and Initials 1), right side (left=350) # Gap B: y=513–582 (between Date of Signing 1 and Signature block), left side (left=106) EXTRA_FIELDS = [ { "name": "Company", "inputType": "TEXT_FIELD", "contentType": "COMPANY", "validation": "NONE", "required": False, "readOnly": False, "locations": [{"pageNumber": 1, "top": 378, "left": 350, "width": 150, "height": 24}], "assignee": "recipient0", }, { "name": "Title", "inputType": "TEXT_FIELD", "contentType": "TITLE", "validation": "NONE", "required": False, "readOnly": False, "locations": [{"pageNumber": 1, "top": 410, "left": 350, "width": 150, "height": 24}], "assignee": "recipient0", }, { "name": "Number Field", "inputType": "TEXT_FIELD", "contentType": "DATA", "validation": "NUMBER", "required": False, "readOnly": False, "locations": [{"pageNumber": 1, "top": 516, "left": 106, "width": 150, "height": 24}], "assignee": "recipient0", }, { "name": "Recipient Email", "inputType": "TEXT_FIELD", "contentType": "SIGNER_EMAIL", "validation": "NONE", "required": False, "readOnly": True, "locations": [{"pageNumber": 1, "top": 548, "left": 106, "width": 175, "height": 24}], "assignee": "recipient0", }, ] def clean_field(f): """Strip server-only keys, keep only what the write API accepts.""" out = {k: v for k, v in f.items() if k in ALLOWED_KEYS} out.setdefault("validation", "NONE") out.setdefault("required", False) out.setdefault("readOnly", False) out.setdefault("masked", False) out.setdefault("maskingText", "*") out.setdefault("calculated", False) out.setdefault("urlOverridable", False) out.setdefault("minLength", -1) out.setdefault("maxLength", -1) out.setdefault("minValue", -1.0) out.setdefault("maxValue", -1.0) out.setdefault("validationErrMsg", "") out.setdefault("conditionalAction", {"anyOrAll": "ANY", "action": "SHOW"}) return out def load_source_fields(): with open(FIELDS_JSON_PATH) as f: data = json.load(f) fields = [clean_field(field) for field in data["fields"]] groups = data.get("formFieldGroups", []) print(f" Loaded {len(fields)} fields from David Tag Demo Form download") return fields, groups def upload_transient_doc(): print(f"Uploading PDF: {PDF_PATH}") with open(PDF_PATH, "rb") as f: files = {"File": ("Tag Demo Form.pdf", f, "application/pdf")} result = adobe_api_post_multipart("transientDocuments", files=files) doc_id = result["transientDocumentId"] print(f" Transient document ID: {doc_id}") return doc_id def create_library_doc(transient_id): print(f"Creating library document '{TEMPLATE_NAME}'...") body = { "fileInfos": [{"transientDocumentId": transient_id}], "name": TEMPLATE_NAME, "templateTypes": ["DOCUMENT", "FORM_FIELD_LAYER"], "sharingMode": "USER", "state": "ACTIVE", } result = adobe_api_post_json("libraryDocuments", body) lib_id = result["id"] print(f" Library document ID: {lib_id}") return lib_id def put_form_fields(lib_id, source_fields, groups): all_fields = source_fields + [clean_field(f) for f in EXTRA_FIELDS] print(f"Writing {len(all_fields)} fields ({len(source_fields)} original + {len(EXTRA_FIELDS)} extra)...") body = {"fields": all_fields} if groups: body["formFieldGroups"] = groups result = adobe_api_put_json(f"libraryDocuments/{lib_id}/formFields", body) saved = len(result.get("fields", [])) print(f" {saved} fields saved.") for field in result.get("fields", []): print(f" {field['inputType']:15} {field.get('contentType',''):20} '{field['name']}'") return result def main(): if not os.path.exists(PDF_PATH): print(f"ERROR: PDF not found at {PDF_PATH}") sys.exit(1) if not os.path.exists(FIELDS_JSON_PATH): print(f"ERROR: form_fields.json not found at {FIELDS_JSON_PATH}") sys.exit(1) source_fields, groups = load_source_fields() transient_id = upload_transient_doc() lib_id = create_library_doc(transient_id) put_form_fields(lib_id, source_fields, groups) print(f"\nDone. Template '{TEMPLATE_NAME}' created (ID: {lib_id})") print("Note: If Company/Title fields need their contentType set, do so in Adobe Sign UI.") if __name__ == "__main__": main()