Skip to content

Commit 5b5134d

Browse files
committed
eg001 working!
1 parent 892fadb commit 5b5134d

29 files changed

Lines changed: 1060 additions & 111 deletions

app/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import os
22
from app import ds_config
33
from flask import Flask
4+
from flask_wtf.csrf import CSRFProtect
5+
46

57
session_path = '/tmp/python_recipe_sessions'
68

79
app = Flask(__name__)
810
app.config.from_pyfile('config.py')
911
app.secret_key = ds_config.DS_CONFIG['session_secret']
12+
csrf = CSRFProtect(app) # See https://flask-wtf.readthedocs.io/en/stable/csrf.html
1013

1114
if 'DYNO' in os.environ: # On Heroku?
1215
import logging

app/eg001_embedded_signing.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"""Example 001: Embedded Signing Ceremony"""
2+
3+
from flask import render_template, url_for, redirect, session, flash, request
4+
from os import path
5+
from app import app, ds_config, views
6+
import base64
7+
from docusign_esign import ApiClient, EnvelopesApi, EnvelopeDefinition, Signer, SignHere, Tabs, Recipients, Document, RecipientViewRequest
8+
9+
eg = "eg001" # reference (and url) for this exampl
10+
signer_client_id = 1000 # Used to indicate that the signer will use an embedded
11+
# Signing Ceremony. Represents the signer's userId within
12+
# your application.
13+
authentication_method = 'None' # How is this application authenticating
14+
# the signer? See the `authenticationMethod' definition
15+
# https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient
16+
17+
demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), 'static/demo_documents'))
18+
19+
20+
def controller():
21+
"""Controller router using the HTTP method"""
22+
if request.method == 'GET':
23+
return get_controller()
24+
elif request.method == 'POST':
25+
return create_controller()
26+
else:
27+
return render_template('404.html'), 404
28+
29+
30+
def create_controller():
31+
"""
32+
1. Check the token
33+
2. Call the worker method
34+
3. Redirect the user to the signing ceremony
35+
"""
36+
minimum_buffer_min = 3
37+
if views.ds_token_ok(minimum_buffer_min):
38+
# 2. Call the worker method
39+
# Data validation would be a good idea here
40+
signer_email = request.form.get('signer_email')
41+
signer_name = request.form.get('signer_name')
42+
envelope_args = {
43+
'signer_email': signer_email,
44+
'signer_name': signer_name,
45+
'signer_client_id': signer_client_id,
46+
'ds_return_url': url_for('ds_return', _external=True),
47+
}
48+
args = {
49+
'account_id': session['ds_account_id'],
50+
'base_path': session['ds_base_path'],
51+
'ds_access_token': session['ds_access_token'],
52+
'envelope_args': envelope_args
53+
}
54+
55+
try:
56+
results = worker(args)
57+
except ImportError as error:
58+
error_body = error and 'response' in error and error['response']['body']
59+
# we can pull the DocuSign error code and message from the response body
60+
error_code = error_body and 'errorCode' in error_body and error_body['errorCode']
61+
error_message = error_body and 'message' in error_body and error_body['message']
62+
63+
# In production, may want to provide customized error messages and
64+
# remediation advice to the user.
65+
return render_template('error.html',
66+
err=error,
67+
error_code=error_code,
68+
error_message=error_message
69+
)
70+
if results:
71+
# Redirect the user to the Signing Ceremony
72+
# Don't use an iFrame!
73+
# State can be stored/recovered using the framework's session or a
74+
# query parameter on the returnUrl (see the makeRecipientViewRequest method)
75+
return redirect(results["redirect_url"])
76+
77+
else:
78+
flash('Sorry, you need to re-authenticate.')
79+
# We could store the parameters of the requested operation
80+
# so it could be restarted automatically.
81+
# But since it should be rare to have a token issue here,
82+
# we'll make the user re-enter the form data after
83+
# authentication.
84+
session['eg'] = url_for(eg)
85+
return redirect(url_for('ds_must_authenticate'))
86+
87+
88+
def worker(args):
89+
"""
90+
1. Create the envelope request object
91+
2. Send the envelope
92+
3. Create the Recipient View request object
93+
4. Obtain the recipient_view_url for the signing ceremony
94+
"""
95+
envelope_args = args["envelope_args"]
96+
# 1. Create the envelope request object
97+
envelope_definition = make_envelope(envelope_args)
98+
99+
# 2. call Envelopes::create API method
100+
# Exceptions will be caught by the calling function
101+
api_client = ApiClient()
102+
api_client.host = args['base_path']
103+
api_client.set_default_header("Authorization", "Bearer " + args['ds_access_token'])
104+
105+
envelope_api = EnvelopesApi(api_client)
106+
results = envelope_api.create_envelope(args['account_id'], envelope_definition=envelope_definition)
107+
108+
envelope_id = results.envelope_id
109+
app.logger.info(f'Envelope was created. EnvelopeId {envelope_id}')
110+
111+
# 3. Create the Recipient View request object
112+
recipient_view_request = RecipientViewRequest(
113+
authentication_method = authentication_method,
114+
client_user_id = envelope_args['signer_client_id'],
115+
recipient_id = '1',
116+
return_url = envelope_args['ds_return_url'],
117+
user_name = envelope_args['signer_name'], email = envelope_args['signer_email']
118+
)
119+
# 4. Obtain the recipient_view_url for the signing ceremony
120+
# Exceptions will be caught by the calling function
121+
results = envelope_api.create_recipient_view(args['account_id'], envelope_id,
122+
recipient_view_request = recipient_view_request)
123+
124+
return {'envelope_id': envelope_id, 'redirect_url': results.url}
125+
126+
# ***DS.worker.end ***DS.snippet.1.end
127+
128+
129+
# ***DS.snippet.2.start
130+
def make_envelope(args):
131+
"""
132+
Creates envelope
133+
args -- parameters for the envelope:
134+
signer_email, signer_name, signer_client_id
135+
returns an envelope definition
136+
"""
137+
138+
# document 1 (pdf) has tag /sn1/
139+
#
140+
# The envelope has one recipient.
141+
# recipient 1 - signer
142+
with open(path.join(demo_docs_path, ds_config.DS_CONFIG['doc_pdf']), "rb") as file:
143+
content_bytes = file.read()
144+
base64_file_content = base64.b64encode(content_bytes).decode('ascii')
145+
146+
# Create the document model
147+
document = Document( # create the DocuSign document object
148+
document_base64 = base64_file_content,
149+
name = 'Example document', # can be different from actual file name
150+
file_extension = 'pdf', # many different document types are accepted
151+
document_id = 1 # a label used to reference the doc
152+
)
153+
154+
# Create the signer recipient model
155+
signer = Signer( # The signer
156+
email = args['signer_email'], name = args['signer_name'],
157+
recipient_id = "1", routing_order = "1",
158+
# Setting the client_user_id marks the signer as embedded
159+
client_user_id = args['signer_client_id']
160+
)
161+
162+
# Create a sign_here tab (field on the document)
163+
sign_here = SignHere( # DocuSign SignHere field/tab
164+
anchor_string = '/sn1/', anchor_units = 'pixels',
165+
anchor_y_offset = '10', anchor_x_offset = '20'
166+
)
167+
168+
# Add the tabs model (including the sign_here tab) to the signer
169+
# The Tabs object wants arrays of the different field/tab types
170+
signer.tabs = Tabs(sign_here_tabs = [sign_here])
171+
172+
# Next, create the top level envelope definition and populate it.
173+
envelope_definition = EnvelopeDefinition(
174+
email_subject = "Please sign this document sent from the Python SDK",
175+
documents = [document],
176+
# The Recipients object wants arrays for each recipient type
177+
recipients = Recipients(signers = [signer]),
178+
status = "sent" # requests that the envelope be created and sent.
179+
)
180+
181+
return envelope_definition
182+
183+
184+
# ***DS.snippet.2.end
185+
186+
187+
def get_controller():
188+
"""responds with the form for the example"""
189+
190+
if views.ds_token_ok():
191+
return render_template("eg001_embedded_signing.html",
192+
title="Embedded Signing Ceremony",
193+
source_file=path.basename(__file__),
194+
source_url=ds_config.DS_CONFIG['github_example_url'] + path.basename(__file__),
195+
documentation=ds_config.DS_CONFIG['documentation'] + eg,
196+
show_doc=ds_config.DS_CONFIG['documentation'],
197+
signer_name=ds_config.DS_CONFIG['signer_name'],
198+
signer_email=ds_config.DS_CONFIG['signer_email']
199+
)
200+
else:
201+
# Save the current operation so it will be resumed after authentication
202+
session['eg'] = url_for(eg)
203+
return redirect(url_for('ds_must_authenticate'))
204+

app/static/demo_documents/order_form.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
<h2 style="font-family: 'Trebuchet MS', Helvetica, sans-serif;
1010
margin-top: 0px;margin-bottom: 3.5em;font-size: 1em;
1111
color: darkblue;">Order Processing Division</h2>
12-
<h4>Ordered by {signerName}</h4>
13-
<p style="margin-top:0em; margin-bottom:0em;">Email: {signerEmail}</p>
12+
<h4>Ordered by {signer_name}</h4>
13+
<p style="margin-top:0em; margin-bottom:0em;">Email: {signer_email}</p>
1414
<p style="margin-top:0em; margin-bottom:0em;">Copy to: {ccName}, {ccEmail}</p>
1515
<p style="margin-top:3em;">
1616
Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps sweet roll pie. Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon drops dragée. Gummi bears cupcake biscuit tiramisu sugar plum pastry. Dragée gummies applicake pudding liquorice. Donut jujubes oat cake jelly-o. Dessert bear claw chocolate cake gummies lollipop sugar plum ice cream gummies cheesecake.

app/templates/404.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44
{% block content %}
55
<h1>File Not Found</h1>
6-
<p><a href="{{ url_for('index') }}">Back</a></p>
6+
<p><a href="{{ url_for('index') }}">Continue</a></p>
77
{% endblock %}

app/templates/base.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828

2929
{% if session['ds_user_name'] %}
3030
<li>
31-
<a class="nav-link" href="/ds/logout" id="logout"
31+
<a class="nav-link" href="{{ url_for('ds_logout') }}" id="logout"
3232
data-busy="href">Logout <span class="sr-only">(current)</span></a>
3333
</li>
3434
{% else %}
3535
<li>
36-
<a class="nav-link" href="/ds/login" id="login"
36+
<a class="nav-link" href="{{ url_for('ds_login') }}" id="login"
3737
data-busy="href">Login <span class="sr-only">(current)</span></a>
3838
</li>
3939
{% endif %}

app/templates/ds_return.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
<h2>Returned data from DocuSign!</h2>
4+
<p>Data:</p>
5+
6+
{% if event %}
7+
<p><b>event: {{ event }}.</b> This event parameter is supplied by DocuSign.
8+
Since it could have been spoofed, don't make business decisions based on
9+
its value. Instead, query DocuSign as appropriate.</p>
10+
{% endif %}
11+
12+
{% if envelope_id %}
13+
<p><b>envelopeId: {{ envelopeId }}.</b> The envelopeId parameter is supplied by DocuSign.
14+
Since it could have been spoofed, don't make business decisions based on
15+
its value. Instead, query DocuSign as appropriate.</p>
16+
{% endif %}
17+
18+
{% if state %}
19+
<p><b>state: {{ state }}.</b> This example state was sent to DocuSign and has now been received back.
20+
It is usually better to store state in your web framework's session.</p>
21+
{% endif %}
22+
23+
<p><a href="/">Continue</a></p>
24+
25+
{% endblock %}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
<h4>1. Embedded Signing Ceremony</h4>
4+
<p>This example sends an envelope, and then uses an embedded signing ceremony for the first signer.</p>
5+
<p>Embedded signing provides a smoother user experience for the signer: the DocuSign signing ceremony is initiated from your website.</p>
6+
7+
{% if show_doc %}
8+
<p><a target='_blank' href='{{ documentation | safe }}'>Documentation</a> about this example.</p>
9+
{% endif %}
10+
11+
<p>API methods used:
12+
<a target ='_blank' href="https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/create">Envelopes::create</a> and
13+
<a target ='_blank' href="https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient">EnvelopeViews::createRecipient</a>.
14+
</p>
15+
<p>
16+
View source file <a target="_blank" href="{{ source_url | safe }}">{{ source_file }}</a> on GitHub.
17+
</p>
18+
19+
<form class="eg" action="" method="post" data-busy="form">
20+
<div class="form-group">
21+
<label for="signer_email">Signer Email</label>
22+
<input type="email" class="form-control" id="signer_email" name="signer_email"
23+
aria-describedby="emailHelp" placeholder="pat@example.com" required
24+
value="{{ signer_email }}">
25+
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
26+
</div>
27+
<div class="form-group">
28+
<label for="signer_name">Signer Name</label>
29+
<input type="text" class="form-control" id="signer_name" placeholder="Pat Johnson" name="signer_name"
30+
value="{{ signer_name }}" required>
31+
</div>
32+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
33+
<button type="submit" class="btn btn-primary">Submit</button>
34+
</form>
35+
36+
{% endblock %}
37+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
<h4>2. Send an envelope with a remote (email) signer and cc recipient</h4>
4+
<p>The envelope includes a pdf, Word, and HTML document. Anchor text
5+
(<a href="https://support.docusign.com/en/guides/AutoPlace-New-DocuSign-Experience">AutoPlace</a>)
6+
is used to position the signing fields in the documents.
7+
</p>
8+
<p>This is a general example of creating and sending an envelope (a signing request) to multiple recipients,
9+
with multiple documents, and with signature fields for the documents.</p>
10+
11+
{% if show_doc %}
12+
<p><a target='_blank' href='{{ documentation | safe }}'>Documentation</a> about this example.</p>
13+
{% endif %}
14+
15+
<p>API method used:
16+
<a target='_blank' href="https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/create">Envelopes::create</a>.
17+
</p>
18+
19+
<p>
20+
View source file <a target="_blank" href="{{ source_url | safe }}">{{ source_file }}</a> on GitHub.
21+
</p>
22+
23+
<form class="eg" action="" method="post" data-busy="form">
24+
<div class="form-group">
25+
<label for="signer_email">Signer Email</label>
26+
<input type="email" class="form-control" id="signer_email" name="signer_email"
27+
aria-describedby="emailHelp" placeholder="pat@example.com" required
28+
value="{{ signer_email }}">
29+
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
30+
</div>
31+
<div class="form-group">
32+
<label for="signer_name">Signer Name</label>
33+
<input type="text" class="form-control" id="signer_name" placeholder="Pat Johnson" name="signer_name"
34+
value="{{ signer_name }}" required>
35+
</div>
36+
<div class="form-group">
37+
<label for="ccEmail">CC Email</label>
38+
<input type="email" class="form-control" id="ccEmail" name="ccEmail"
39+
aria-describedby="emailHelp" placeholder="pat@example.com" required
40+
<small id="emailHelp" class="form-text text-muted">The email for the cc recipient must be different from the signer's email.</small>
41+
</div>
42+
<div class="form-group">
43+
<label for="ccName">CC Name</label>
44+
<input type="text" class="form-control" id="ccName" placeholder="Pat Johnson" name="ccName"
45+
required>
46+
</div>
47+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
48+
<button type="submit" class="btn btn-primary">Submit</button>
49+
</form>
50+
51+
{% endblock %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
<h4>3. List envelopes in the user's account</h4>
4+
<p>List the envelopes created in the last 30 days.</p>
5+
<p>This example demonstrates how to query DocuSign about envelopes sent by the current user.</p>
6+
7+
{% if show_doc %}
8+
<p><a target='_blank' href='{{ documentation | safe }}'>Documentation</a> about this example.</p>
9+
{% endif %}
10+
11+
<p>API method used:
12+
<a target='_blank' href="https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/listStatusChanges">Envelopes::listStatusChanges</a>.
13+
</p>
14+
<p>
15+
View source file <a target="_blank" href="{{ source_url | safe }}">{{ source_file }}</a> on GitHub.
16+
</p>
17+
18+
<form class="eg" action="" method="post" data-busy="form">
19+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
20+
<button type="submit" class="btn btn-primary">Continue</button>
21+
</form>
22+
23+
{% endblock %}

0 commit comments

Comments
 (0)