| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7add73e commit 91ca61b
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ | |||
| 14 | 14 | from .connect import views as connect_views | |
| 15 | 15 | from .webforms import views as webforms_views | |
| 16 | 16 | from .notary import views as notary_views | |
| 17 | + from .connected_fields import views as connected_fields_views | ||
| 17 | 18 | from .views import core | |
| 18 | 19 | ||
| 19 | 20 | session_path = "/tmp/python_recipe_sessions" | |
@@ -120,6 +121,8 @@ | |||
| 120 | 121 | ||
| 121 | 122 | app.register_blueprint(notary_views.neg004) | |
| 122 | 123 | ||
| 124 | + app.register_blueprint(connected_fields_views.feg001) | ||
| 125 | + | ||
| 123 | 126 | if "DYNO" in os.environ: # On Heroku? | |
| 124 | 127 | import logging | |
| 125 | 128 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,235 @@ | |||
| 1 | + import base64 | ||
| 2 | + import requests | ||
| 3 | + from os import path | ||
| 4 | + | ||
| 5 | + from docusign_esign import EnvelopesApi, Text, Document, Signer, EnvelopeDefinition, SignHere, Tabs, \ | ||
| 6 | + Recipients | ||
| 7 | + from flask import session, request | ||
| 8 | + | ||
| 9 | + from ...consts import demo_docs_path, pattern | ||
| 10 | + from ...docusign import create_api_client | ||
| 11 | + from ...ds_config import DS_CONFIG | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + class Eg001SetConnectedFieldsController: | ||
| 15 | + @staticmethod | ||
| 16 | + def get_args(): | ||
| 17 | + """Get request and session arguments""" | ||
| 18 | + # Parse request arguments | ||
| 19 | + signer_email = pattern.sub("", request.form.get("signer_email")) | ||
| 20 | + signer_name = pattern.sub("", request.form.get("signer_name")) | ||
| 21 | + selected_app_id = pattern.sub("", request.form.get("app_id")) | ||
| 22 | + envelope_args = { | ||
| 23 | + "signer_email": signer_email, | ||
| 24 | + "signer_name": signer_name, | ||
| 25 | + } | ||
| 26 | + args = { | ||
| 27 | + "account_id": session["ds_account_id"], | ||
| 28 | + "base_path": session["ds_base_path"], | ||
| 29 | + "access_token": session["ds_access_token"], | ||
| 30 | + "selected_app_id": selected_app_id, | ||
| 31 | + "envelope_args": envelope_args | ||
| 32 | + } | ||
| 33 | + return args | ||
| 34 | + | ||
| 35 | + @staticmethod | ||
| 36 | + def get_tab_groups(args): | ||
| 37 | + """ | ||
| 38 | + 1. Get the list of tab groups | ||
| 39 | + 2. Filter by action contract and tab label | ||
| 40 | + 3. Create a list of unique apps | ||
| 41 | + """ | ||
| 42 | + | ||
| 43 | + #ds-snippet-start:ConnectedFields1Step2 | ||
| 44 | + headers = { | ||
| 45 | + "Authorization": "Bearer " + args['access_token'], | ||
| 46 | + "Accept": "application/json", | ||
| 47 | + "Content-Type": "application/json" | ||
| 48 | + } | ||
| 49 | + #ds-snippet-end:ConnectedFields1Step2 | ||
| 50 | + | ||
| 51 | + #ds-snippet-start:ConnectedFields1Step3 | ||
| 52 | + url = f"{args['base_path']}/v1/accounts/{args['account_id']}/connected-fields/tab-groups" | ||
| 53 | + | ||
| 54 | + response = requests.get(url, headers=headers) | ||
| 55 | + response_data = response.json() | ||
| 56 | + | ||
| 57 | + filtered_apps = list( | ||
| 58 | + app for app in response_data | ||
| 59 | + if any( | ||
| 60 | + ("extensionData" in tab and "actionContract" in tab["extensionData"] and "Verify" in tab["extensionData"]["actionContract"]) or | ||
| 61 | + ("tabLabel" in tab and "connecteddata" in tab["tabLabel"]) | ||
| 62 | + for tab in app.get("tabs", []) | ||
| 63 | + ) | ||
| 64 | + ) | ||
| 65 | + | ||
| 66 | + unique_apps = list({app['appId']: app for app in filtered_apps}.values()) | ||
| 67 | + #ds-snippet-end:ConnectedFields1Step3 | ||
| 68 | + | ||
| 69 | + return unique_apps | ||
| 70 | + | ||
| 71 | + @staticmethod | ||
| 72 | + #ds-snippet-start:ConnectedFields1Step4 | ||
| 73 | + def extract_verification_data(selected_app_id, tab): | ||
| 74 | + extension_data = tab["extensionData"] | ||
| 75 | + | ||
| 76 | + return { | ||
| 77 | + "app_id": selected_app_id, | ||
| 78 | + "extension_group_id": extension_data["extensionGroupId"] if "extensionGroupId" in extension_data else "", | ||
| 79 | + "publisher_name": extension_data["publisherName"] if "publisherName" in extension_data else "", | ||
| 80 | + "application_name": extension_data["applicationName"] if "applicationName" in extension_data else "", | ||
| 81 | + "action_name": extension_data["actionName"] if "actionName" in extension_data else "", | ||
| 82 | + "action_input_key": extension_data["actionInputKey"] if "actionInputKey" in extension_data else "", | ||
| 83 | + "action_contract": extension_data["actionContract"] if "actionContract" in extension_data else "", | ||
| 84 | + "extension_name": extension_data["extensionName"] if "extensionName" in extension_data else "", | ||
| 85 | + "extension_contract": extension_data["extensionContract"] if "extensionContract" in extension_data else "", | ||
| 86 | + "required_for_extension": extension_data["requiredForExtension"] if "requiredForExtension" in extension_data else "", | ||
| 87 | + "tab_label": tab["tabLabel"], | ||
| 88 | + "connection_key": ( | ||
| 89 | + extension_data["connectionInstances"][0]["connectionKey"] | ||
| 90 | + if "connectionInstances" in extension_data and extension_data["connectionInstances"] | ||
| 91 | + else "" | ||
| 92 | + ), | ||
| 93 | + "connection_value": ( | ||
| 94 | + extension_data["connectionInstances"][0]["connectionValue"] | ||
| 95 | + if "connectionInstances" in extension_data and extension_data["connectionInstances"] | ||
| 96 | + else "" | ||
| 97 | + ), | ||
| 98 | + } | ||
| 99 | + #ds-snippet-end:ConnectedFields1Step4 | ||
| 100 | + | ||
| 101 | + @classmethod | ||
| 102 | + def send_envelope(cls, args, app): | ||
| 103 | + """ | ||
| 104 | + 1. Create the envelope request object | ||
| 105 | + 2. Send the envelope | ||
| 106 | + 3. Obtain the envelope_id | ||
| 107 | + """ | ||
| 108 | + #ds-snippet-start:ConnectedFields1Step6 | ||
| 109 | + envelope_args = args["envelope_args"] | ||
| 110 | + # Create the envelope request object | ||
| 111 | + envelope_definition = cls.make_envelope(envelope_args, app) | ||
| 112 | + | ||
| 113 | + # Call Envelopes::create API method | ||
| 114 | + # Exceptions will be caught by the calling function | ||
| 115 | + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) | ||
| 116 | + | ||
| 117 | + envelope_api = EnvelopesApi(api_client) | ||
| 118 | + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) | ||
| 119 | + | ||
| 120 | + envelope_id = results.envelope_id | ||
| 121 | + #ds-snippet-end:ConnectedFields1Step6 | ||
| 122 | + | ||
| 123 | + return {"envelope_id": envelope_id} | ||
| 124 | + | ||
| 125 | + @classmethod | ||
| 126 | + #ds-snippet-start:ConnectedFields1Step5 | ||
| 127 | + def make_envelope(cls, args, app): | ||
| 128 | + """ | ||
| 129 | + Creates envelope | ||
| 130 | + args -- parameters for the envelope: | ||
| 131 | + signer_email, signer_name | ||
| 132 | + returns an envelope definition | ||
| 133 | + """ | ||
| 134 | + | ||
| 135 | + # document 1 (pdf) has tag /sn1/ | ||
| 136 | + # | ||
| 137 | + # The envelope has one recipient. | ||
| 138 | + # recipient 1 - signer | ||
| 139 | + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: | ||
| 140 | + content_bytes = file.read() | ||
| 141 | + base64_file_content = base64.b64encode(content_bytes).decode("ascii") | ||
| 142 | + | ||
| 143 | + # Create the document model | ||
| 144 | + document = Document( # create the DocuSign document object | ||
| 145 | + document_base64=base64_file_content, | ||
| 146 | + name="Example document", # can be different from actual file name | ||
| 147 | + file_extension="pdf", # many different document types are accepted | ||
| 148 | + document_id=1 # a label used to reference the doc | ||
| 149 | + ) | ||
| 150 | + | ||
| 151 | + # Create the signer recipient model | ||
| 152 | + signer = Signer( | ||
| 153 | + # The signer | ||
| 154 | + email=args["signer_email"], | ||
| 155 | + name=args["signer_name"], | ||
| 156 | + recipient_id="1", | ||
| 157 | + routing_order="1" | ||
| 158 | + ) | ||
| 159 | + | ||
| 160 | + # Create a sign_here tab (field on the document) | ||
| 161 | + sign_here = SignHere( | ||
| 162 | + anchor_string="/sn1/", | ||
| 163 | + anchor_units="pixels", | ||
| 164 | + anchor_y_offset="10", | ||
| 165 | + anchor_x_offset="20" | ||
| 166 | + ) | ||
| 167 | + | ||
| 168 | + # Create text tabs (field on the document) | ||
| 169 | + text_tabs = [] | ||
| 170 | + for tab in (t for t in app["tabs"] if "SuggestionInput" not in t["tabLabel"]): | ||
| 171 | + verification_data = cls.extract_verification_data(app["appId"], tab) | ||
| 172 | + extension_data = cls.get_extension_data(verification_data) | ||
| 173 | + | ||
| 174 | + text_tab = { | ||
| 175 | + "requireInitialOnSharedChange": False, | ||
| 176 | + "requireAll": False, | ||
| 177 | + "name": verification_data["application_name"], | ||
| 178 | + "required": True, | ||
| 179 | + "locked": False, | ||
| 180 | + "disableAutoSize": False, | ||
| 181 | + "maxLength": 4000, | ||
| 182 | + "tabLabel": verification_data["tab_label"], | ||
| 183 | + "font": "lucidaconsole", | ||
| 184 | + "fontColor": "black", | ||
| 185 | + "fontSize": "size9", | ||
| 186 | + "documentId": "1", | ||
| 187 | + "recipientId": "1", | ||
| 188 | + "pageNumber": "1", | ||
| 189 | + "xPosition": f"{70 + 100 * int(len(text_tabs) / 10)}", | ||
| 190 | + "yPosition": f"{560 + 20 * (len(text_tabs) % 10)}", | ||
| 191 | + "width": "84", | ||
| 192 | + "height": "22", | ||
| 193 | + "templateRequired": False, | ||
| 194 | + "tabType": "text", | ||
| 195 | + "tooltip": verification_data["action_input_key"], | ||
| 196 | + "extensionData": extension_data | ||
| 197 | + } | ||
| 198 | + text_tabs.append(text_tab) | ||
| 199 | + | ||
| 200 | + # Add the tabs model (including the sign_here and text tabs) to the signer | ||
| 201 | + # The Tabs object wants arrays of the different field/tab types | ||
| 202 | + signer.tabs = Tabs(sign_here_tabs=[sign_here], text_tabs=text_tabs) | ||
| 203 | + | ||
| 204 | + # Next, create the top level envelope definition and populate it. | ||
| 205 | + envelope_definition = EnvelopeDefinition( | ||
| 206 | + email_subject="Please sign this document", | ||
| 207 | + documents=[document], | ||
| 208 | + # The Recipients object wants arrays for each recipient type | ||
| 209 | + recipients=Recipients(signers=[signer]), | ||
| 210 | + status="sent" # requests that the envelope be created and sent. | ||
| 211 | + ) | ||
| 212 | + | ||
| 213 | + return envelope_definition | ||
| 214 | + | ||
| 215 | + def get_extension_data(verification_data): | ||
| 216 | + return { | ||
| 217 | + "extensionGroupId": verification_data["extension_group_id"], | ||
| 218 | + "publisherName": verification_data["publisher_name"], | ||
| 219 | + "applicationId": verification_data["app_id"], | ||
| 220 | + "applicationName": verification_data["application_name"], | ||
| 221 | + "actionName": verification_data["action_name"], | ||
| 222 | + "actionContract": verification_data["action_contract"], | ||
| 223 | + "extensionName": verification_data["extension_name"], | ||
| 224 | + "extensionContract": verification_data["extension_contract"], | ||
| 225 | + "requiredForExtension": verification_data["required_for_extension"], | ||
| 226 | + "actionInputKey": verification_data["action_input_key"], | ||
| 227 | + "extensionPolicy": 'MustVerifyToSign', | ||
| 228 | + "connectionInstances": [ | ||
| 229 | + { | ||
| 230 | + "connectionKey": verification_data["connection_key"], | ||
| 231 | + "connectionValue": verification_data["connection_value"], | ||
| 232 | + } | ||
| 233 | + ] | ||
| 234 | + } | ||
| 235 | + #ds-snippet-end:ConnectedFields1Step5 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + from .eg001_set_connected_fields import feg001 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,83 @@ | |||
| 1 | + """Example 001: Set connected fields""" | ||
| 2 | + | ||
| 3 | + from docusign_esign.client.api_exception import ApiException | ||
| 4 | + from flask import render_template, redirect, Blueprint, session | ||
| 5 | + | ||
| 6 | + from ..examples.eg001_set_connected_fields import Eg001SetConnectedFieldsController | ||
| 7 | + from ...docusign import authenticate, ensure_manifest, get_example_by_number | ||
| 8 | + from ...ds_config import DS_CONFIG | ||
| 9 | + from ...error_handlers import process_error | ||
| 10 | + from ...consts import API_TYPE | ||
| 11 | + | ||
| 12 | + example_number = 1 | ||
| 13 | + api = API_TYPE["CONNECTED_FIELDS"] | ||
| 14 | + eg = f"feg00{example_number}" # reference (and url) for this example | ||
| 15 | + feg001 = Blueprint(eg, __name__) | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + @feg001.route(f"/{eg}", methods=["POST"]) | ||
| 19 | + @ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"]) | ||
| 20 | + @authenticate(eg=eg, api=api) | ||
| 21 | + def set_connected_fields(): | ||
| 22 | + """ | ||
| 23 | + 1. Get required arguments | ||
| 24 | + 2. Call the worker method | ||
| 25 | + """ | ||
| 26 | + try: | ||
| 27 | + # 1. Get required arguments | ||
| 28 | + args = Eg001SetConnectedFieldsController.get_args() | ||
| 29 | + # 2. Call the worker method | ||
| 30 | + selected_app = next((app for app in session["apps"] if app["appId"] == args["selected_app_id"]), None) | ||
| 31 | + results = Eg001SetConnectedFieldsController.send_envelope(args, selected_app) | ||
| 32 | + except ApiException as err: | ||
| 33 | + return process_error(err) | ||
| 34 | + | ||
| 35 | + session["envelope_id"] = results["envelope_id"] | ||
| 36 | + | ||
| 37 | + example = get_example_by_number(session["manifest"], example_number, api) | ||
| 38 | + return render_template( | ||
| 39 | + "example_done.html", | ||
| 40 | + title=example["ExampleName"], | ||
| 41 | + message=example["ResultsPageText"].format(results['envelope_id']) | ||
| 42 | + ) | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + @feg001.route(f"/{eg}", methods=["GET"]) | ||
| 46 | + @ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"]) | ||
| 47 | + @authenticate(eg=eg, api=api) | ||
| 48 | + def get_view(): | ||
| 49 | + """responds with the form for the example""" | ||
| 50 | + example = get_example_by_number(session["manifest"], example_number, api) | ||
| 51 | + | ||
| 52 | + args = { | ||
| 53 | + "account_id": session["ds_account_id"], | ||
| 54 | + "base_path": "https://api-d.docusign.com", | ||
| 55 | + "access_token": session["ds_access_token"], | ||
| 56 | + } | ||
| 57 | + apps = Eg001SetConnectedFieldsController.get_tab_groups(args) | ||
| 58 | + | ||
| 59 | + if not apps or len(apps) == 0: | ||
| 60 | + additional_page_data = next( | ||
| 61 | + (p for p in example["AdditionalPage"] if p["Name"] == "no_verification_app"), | ||
| 62 | + None | ||
| 63 | + ) | ||
| 64 | + | ||
| 65 | + return render_template( | ||
| 66 | + "example_done.html", | ||
| 67 | + title=example["ExampleName"], | ||
| 68 | + message=additional_page_data["ResultsPageText"] | ||
| 69 | + ) | ||
| 70 | + | ||
| 71 | + session["apps"] = apps | ||
| 72 | + return render_template( | ||
| 73 | + "connected_fields/eg001_set_connected_fields.html", | ||
| 74 | + title=example["ExampleName"], | ||
| 75 | + example=example, | ||
| 76 | + apps=apps, | ||
| 77 | + source_file="eg001_set_connected_fields.py", | ||
| 78 | + source_url=DS_CONFIG["github_example_url"] + "eg001_set_connected_fields.py", | ||
| 79 | + documentation=DS_CONFIG["documentation"] + eg, | ||
| 80 | + show_doc=DS_CONFIG["documentation"], | ||
| 81 | + signer_name=DS_CONFIG["signer_name"], | ||
| 82 | + signer_email=DS_CONFIG["signer_email"] | ||
| 83 | + ) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -117,5 +117,6 @@ | |||
| 117 | 117 | "ADMIN": "Admin", | |
| 118 | 118 | "CONNECT": "Connect", | |
| 119 | 119 | "WEBFORMS": "WebForms", | |
| 120 | - "NOTARY": "Notary" | ||
| 120 | + "NOTARY": "Notary", | ||
| 121 | + "CONNECTED_FIELDS": "ConnectedFields" | ||
| 121 | 122 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,6 +46,10 @@ | |||
| 46 | 46 | "signature", "organization_read", "notary_read", "notary_write" | |
| 47 | 47 | ] | |
| 48 | 48 | ||
| 49 | + CONNECTED_FIELDS = [ | ||
| 50 | + "signature", "adm_store_unified_repo_read" | ||
| 51 | + ] | ||
| 52 | + | ||
| 49 | 53 | ||
| 50 | 54 | class DSClient: | |
| 51 | 55 | ds_app = None | |
@@ -77,6 +81,8 @@ def _auth_code_grant(cls, api): | |||
| 77 | 81 | use_scopes.extend(WEBFORMS_SCOPES) | |
| 78 | 82 | elif api == "Notary": | |
| 79 | 83 | use_scopes.extend(NOTARY_SCOPES) | |
| 84 | + elif api == "ConnectedFields": | ||
| 85 | + use_scopes.extend(CONNECTED_FIELDS) | ||
| 80 | 86 | else: | |
| 81 | 87 | use_scopes.extend(SCOPES) | |
| 82 | 88 | # remove duplicate scopes | |
@@ -115,6 +121,8 @@ def _pkce_auth(cls, api): | |||
| 115 | 121 | use_scopes.extend(WEBFORMS_SCOPES) | |
| 116 | 122 | elif api == "Notary": | |
| 117 | 123 | use_scopes.extend(NOTARY_SCOPES) | |
| 124 | + elif api == "ConnectedFields": | ||
| 125 | + use_scopes.extend(CONNECTED_FIELDS) | ||
| 118 | 126 | else: | |
| 119 | 127 | use_scopes.extend(SCOPES) | |
| 120 | 128 | # remove duplicate scopes | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,8 @@ const DS_SEARCH = (function () { | |||
| 7 | 7 | ADMIN: "admin", | |
| 8 | 8 | CONNECT: "connect", | |
| 9 | 9 | WEBFORMS: "webforms", | |
| 10 | - NOTARY: "notary" | ||
| 10 | + NOTARY: "notary", | ||
| 11 | + CONNECTED_FIELDS: "connectedfields", | ||
| 11 | 12 | } | |
| 12 | 13 | ||
| 13 | 14 | const processJSONData = function () { | |
@@ -148,6 +149,8 @@ const DS_SEARCH = (function () { | |||
| 148 | 149 | return "weg"; | |
| 149 | 150 | case API_TYPES.NOTARY: | |
| 150 | 151 | return "neg"; | |
| 152 | + case API_TYPES.CONNECTED_FIELDS: | ||
| 153 | + return "feg"; | ||
| 151 | 154 | } | |
| 152 | 155 | } | |
| 153 | 156 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments