Skip to content

Commit 5d1d778

Browse files
authored
Merge pull request #135 from docusign/feature/focused-view-code-example
Added focused view code example
2 parents 1f639ab + eb22611 commit 5d1d778

File tree

6 files changed

+325
-0
lines changed

6 files changed

+325
-0
lines changed

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
app.register_blueprint(esignature_views.eg041)
109109
app.register_blueprint(esignature_views.eg042)
110110
app.register_blueprint(esignature_views.eg043)
111+
app.register_blueprint(esignature_views.eg044)
111112

112113
if "DYNO" in os.environ: # On Heroku?
113114
import logging
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import base64
2+
from os import path
3+
4+
from docusign_esign import EnvelopesApi, RecipientViewRequest, Document, Signer, EnvelopeDefinition, SignHere, Tabs, \
5+
Recipients
6+
from flask import session, url_for, request
7+
8+
from ...consts import authentication_method, demo_docs_path, pattern, signer_client_id
9+
from ...docusign import create_api_client
10+
from ...ds_config import DS_CONFIG
11+
12+
13+
class Eg044FocusedViewController:
14+
@staticmethod
15+
def get_args():
16+
"""Get request and session arguments"""
17+
# More data validation would be a good idea here
18+
# Strip anything other than characters listed
19+
# 1. Parse request arguments
20+
signer_email = pattern.sub("", request.form.get("signer_email"))
21+
signer_name = pattern.sub("", request.form.get("signer_name"))
22+
envelope_args = {
23+
"signer_email": signer_email,
24+
"signer_name": signer_name,
25+
"signer_client_id": signer_client_id,
26+
"ds_return_url": url_for("ds.ds_return", _external=True),
27+
}
28+
args = {
29+
"account_id": session["ds_account_id"],
30+
"base_path": session["ds_base_path"],
31+
"access_token": session["ds_access_token"],
32+
"envelope_args": envelope_args
33+
}
34+
return args
35+
36+
@classmethod
37+
def worker(cls, args):
38+
"""
39+
1. Create the envelope request object
40+
2. Send the envelope
41+
3. Create the Recipient View request object
42+
4. Obtain the recipient_view_url for the embedded signing
43+
"""
44+
#ds-snippet-start:eSign44Step3
45+
envelope_args = args["envelope_args"]
46+
# 1. Create the envelope request object
47+
envelope_definition = cls.make_envelope(envelope_args)
48+
49+
# 2. call Envelopes::create API method
50+
# Exceptions will be caught by the calling function
51+
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
52+
53+
envelope_api = EnvelopesApi(api_client)
54+
results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition)
55+
56+
envelope_id = results.envelope_id
57+
#ds-snippet-end:eSign44Step3
58+
59+
# 3. Create the Recipient View request object
60+
#ds-snippet-start:eSign44Step4
61+
recipient_view_request = RecipientViewRequest(
62+
authentication_method=authentication_method,
63+
client_user_id=envelope_args["signer_client_id"],
64+
recipient_id="1",
65+
return_url=envelope_args["ds_return_url"],
66+
user_name=envelope_args["signer_name"],
67+
email=envelope_args["signer_email"],
68+
frame_ancestors=["http://localhost:3000", "https://apps-d.docusign.com"],
69+
message_origins=["https://apps-d.docusign.com"]
70+
)
71+
#ds-snippet-end:eSign44Step4
72+
73+
# 4. Obtain the recipient_view_url for the embedded signing
74+
# Exceptions will be caught by the calling function
75+
76+
#ds-snippet-start:eSign44Step5
77+
results = envelope_api.create_recipient_view(
78+
account_id=args["account_id"],
79+
envelope_id=envelope_id,
80+
recipient_view_request=recipient_view_request
81+
)
82+
83+
return {"envelope_id": envelope_id, "redirect_url": results.url}
84+
#ds-snippet-end:eSign44Step5
85+
86+
@classmethod
87+
#ds-snippet-start:eSign44Step2
88+
def make_envelope(cls, args):
89+
"""
90+
Creates envelope
91+
args -- parameters for the envelope:
92+
signer_email, signer_name, signer_client_id
93+
returns an envelope definition
94+
"""
95+
96+
# document 1 (pdf) has tag /sn1/
97+
#
98+
# The envelope has one recipient.
99+
# recipient 1 - signer
100+
with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file:
101+
content_bytes = file.read()
102+
base64_file_content = base64.b64encode(content_bytes).decode("ascii")
103+
104+
# Create the document model
105+
document = Document( # create the DocuSign document object
106+
document_base64=base64_file_content,
107+
name="Example document", # can be different from actual file name
108+
file_extension="pdf", # many different document types are accepted
109+
document_id=1 # a label used to reference the doc
110+
)
111+
112+
# Create the signer recipient model
113+
signer = Signer(
114+
# The signer
115+
email=args["signer_email"],
116+
name=args["signer_name"],
117+
recipient_id="1",
118+
routing_order="1",
119+
# Setting the client_user_id marks the signer as embedded
120+
client_user_id=args["signer_client_id"]
121+
)
122+
123+
# Create a sign_here tab (field on the document)
124+
sign_here = SignHere(
125+
# DocuSign SignHere field/tab
126+
anchor_string="/sn1/",
127+
anchor_units="pixels",
128+
anchor_y_offset="10",
129+
anchor_x_offset="20"
130+
)
131+
132+
# Add the tabs model (including the sign_here tab) to the signer
133+
# The Tabs object wants arrays of the different field/tab types
134+
signer.tabs = Tabs(sign_here_tabs=[sign_here])
135+
136+
# Next, create the top level envelope definition and populate it.
137+
envelope_definition = EnvelopeDefinition(
138+
email_subject="Please sign this document sent from the Python SDK",
139+
documents=[document],
140+
# The Recipients object wants arrays for each recipient type
141+
recipients=Recipients(signers=[signer]),
142+
status="sent" # requests that the envelope be created and sent.
143+
)
144+
145+
return envelope_definition
146+
#ds-snippet-end:eSign44Step2

app/eSignature/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@
4040
from .eg041_cfr_embedded_signing import eg041
4141
from .eg042_document_generation import eg042
4242
from .eg043_shared_access import eg043
43+
from .eg044_focused_view import eg044
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Example 044: Focused view"""
2+
3+
from docusign_esign.client.api_exception import ApiException
4+
from flask import render_template, Blueprint, session
5+
6+
from ..examples.eg044_focused_view import Eg044FocusedViewController
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 = 44
13+
api = API_TYPE["ESIGNATURE"]
14+
eg = f"eg0{example_number}" # reference (and url) for this example
15+
eg044 = Blueprint(eg, __name__)
16+
17+
18+
@eg044.route(f"/{eg}", methods=["POST"])
19+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
20+
@authenticate(eg=eg, api=api)
21+
def embedded_signing():
22+
"""
23+
1. Get required arguments
24+
2. Call the worker method
25+
3. Redirect the user to the embedded signing
26+
"""
27+
try:
28+
# 1. Get required arguments
29+
args = Eg044FocusedViewController.get_args()
30+
# 2. Call the worker method
31+
results = Eg044FocusedViewController.worker(args)
32+
except ApiException as err:
33+
return process_error(err)
34+
35+
session["envelope_id"] = results["envelope_id"]
36+
example = get_example_by_number(session["manifest"], example_number, api)
37+
38+
return render_template(
39+
"eSignature/eg044_embed.html",
40+
title=example["ExampleName"],
41+
url=results["redirect_url"],
42+
integration_key=DS_CONFIG["ds_client_id"]
43+
)
44+
45+
46+
@eg044.route(f"/{eg}", methods=["GET"])
47+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
48+
@authenticate(eg=eg, api=api)
49+
def get_view():
50+
"""responds with the form for the example"""
51+
example = get_example_by_number(session["manifest"], example_number, api)
52+
53+
return render_template(
54+
"eSignature/eg044_focused_view.html",
55+
title=example["ExampleName"],
56+
example=example,
57+
source_file="eg044_focused_view.py",
58+
source_url=DS_CONFIG["github_example_url"] + "eg044_focused_view.py",
59+
documentation=DS_CONFIG["documentation"] + eg,
60+
show_doc=DS_CONFIG["documentation"],
61+
signer_name=DS_CONFIG["signer_name"],
62+
signer_email=DS_CONFIG["signer_email"]
63+
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
<br />
4+
<h2>The document has been embedded with focused view.</h2>
5+
<br />
6+
7+
<!--
8+
//ds-snippet-start:eSign44Step6
9+
-->
10+
11+
<!DOCTYPE html>
12+
<html>
13+
<head>
14+
<meta charset="utf-8" />
15+
<title>Signing</title>
16+
<style>
17+
html,
18+
body {
19+
padding: 0;
20+
margin: 0;
21+
font: 13px Helvetica, Arial, sans-serif;
22+
}
23+
24+
.docusign-agreement {
25+
width: 75%;
26+
height: 800px;
27+
}
28+
</style>
29+
</head>
30+
<body>
31+
<div class="docusign-agreement" id="agreement"></div>
32+
<script src="script.js"></script>
33+
</body>
34+
</html>
35+
36+
<p><a href="/">Continue</a></p>
37+
38+
<script src='https://docucdn-a.akamaihd.net/demo/1ds/libs/@embedded-js/core/latest/dist/bundle.js'></script>
39+
<script>
40+
window.DocuSign.loadDocuSign('{{ integration_key }}')
41+
.then((docusign) => {
42+
const signing = docusign.signing({
43+
url: '{{ url }}',
44+
displayFormat: 'focused',
45+
style: {
46+
/** High-level variables that mirror our existing branding APIs. Reusing the branding name here for familiarity. */
47+
branding: {
48+
primaryButton: {
49+
/** Background color of primary button */
50+
backgroundColor: '#333',
51+
/** Text color of primary button */
52+
color: '#fff',
53+
}
54+
},
55+
56+
/** High-level components we allow specific overrides for */
57+
signingNavigationButton: {
58+
finishText: 'You have finished the document! Hooray!',
59+
position: 'bottom-center'
60+
}
61+
}
62+
});
63+
64+
signing.on('ready', (event) => {
65+
console.log('UI is rendered');
66+
});
67+
68+
signing.on('sessionEnd', (event) => {
69+
/** The event here denotes what caused the sessionEnd to trigger, such as signing_complete, ttl_expired etc../ **/
70+
console.log('sessionend', event);
71+
});
72+
73+
signing.mount('#agreement');
74+
})
75+
.catch((ex) => {
76+
// Any configuration or API limits will be caught here
77+
});</script>
78+
79+
<!--
80+
//ds-snippet-end:eSign44Step6
81+
-->
82+
83+
{% endblock %}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
{% include 'example_info.html' %}
4+
5+
{% set signer_form_index = 0 %}
6+
{% set signer_email_index = 0 %}
7+
{% set signer_name_index = 1 %}
8+
9+
<form class="eg" action="" method="post" data-busy="form">
10+
{% if 'FormName' in example['Forms'][signer_form_index] %}
11+
<p>{{ example['Forms'][signer_form_index]['FormName'] | safe }}</p>
12+
{% endif %}
13+
14+
<div class="form-group">
15+
<label for="signer_email">{{ example['Forms'][signer_form_index]['Inputs'][signer_email_index]['InputName'] }}</label>
16+
<input type="email" class="form-control" id="signer_email" name="signer_email"
17+
aria-describedby="emailHelp" placeholder="{{ example['Forms'][signer_form_index]['Inputs'][signer_email_index]['InputPlaceholder'] }}" required
18+
value="{{ signer_email }}">
19+
<small id="emailHelp" class="form-text text-muted">{{ session['manifest']['SupportingTexts']['HelpingTexts']['EmailWontBeShared'] | safe}}</small>
20+
</div>
21+
<div class="form-group">
22+
<label for="signer_name">{{ example['Forms'][signer_form_index]['Inputs'][signer_name_index]['InputName'] }}</label>
23+
<input type="text" class="form-control" id="signer_name" placeholder="{{ example['Forms'][signer_form_index]['Inputs'][signer_name_index]['InputPlaceholder'] }}" name="signer_name"
24+
value="{{ signer_name }}" required>
25+
</div>
26+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
27+
{% include 'submit_button.html' %}
28+
</form>
29+
30+
{% endblock %}
31+

0 commit comments

Comments
 (0)