Skip to content

Commit 4b6d75a

Browse files
committed
eg 7 works
1 parent 1d86598 commit 4b6d75a

File tree

3 files changed

+166
-179
lines changed

3 files changed

+166
-179
lines changed

app/eg007_envelope_get_doc.py

Lines changed: 153 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,159 @@
1-
/**
2-
* @file
3-
* Example 007: Get a document from an envelope
4-
* @author DocuSign
5-
*/
6-
7-
const path = require('path')
8-
, docusign = require('docusign-esign')
9-
, dsConfig = require('../../ds_configuration.js').config
10-
, validator = require('validator')
11-
, stream = require('stream')
12-
, {promisify} = require('util') // http://2ality.com/2017/05/util-promisify.html
13-
;
14-
15-
const eg007EnvelopeGetDoc = exports
16-
, eg = 'eg007' // This example reference.
17-
, mustAuthenticate = '/ds/mustAuthenticate'
18-
, minimumBufferMin = 3
19-
;
20-
21-
/**
22-
* Form page for this application
23-
*/
24-
eg007EnvelopeGetDoc.getController = (req, res) => {
25-
// Check that the authentication token is ok with a long buffer time.
26-
// If needed, now is the best time to ask the user to authenticate
27-
// since they have not yet entered any information into the form.
28-
let tokenOK = req.dsAuthCodeGrant.checkToken();
29-
if (tokenOK) {
30-
let envelopeDocuments = req.session.envelopeDocuments,
31-
documentOptions;
32-
if (envelopeDocuments) {
33-
// Prepare the select items
34-
documentOptions = envelopeDocuments.documents.map ( item =>
35-
({text: item.name, documentId: item.documentId}));
36-
}
37-
res.render('pages/examples/eg007EnvelopeGetDoc', {
38-
csrfToken: req.csrfToken(),
39-
title: "Download a document",
40-
envelopeOk: req.session.envelopeId,
41-
documentsOk: envelopeDocuments,
42-
documentOptions: documentOptions,
43-
sourceFile: path.basename(__filename),
44-
sourceUrl: dsConfig.githubExampleUrl + path.basename(__filename),
45-
documentation: dsConfig.documentation + eg,
46-
showDoc: dsConfig.documentation
47-
});
48-
} else {
49-
// Save the current operation so it will be resumed after authentication
50-
req.dsAuthCodeGrant.setEg(req, eg);
51-
res.redirect(mustAuthenticate);
52-
}
53-
}
54-
55-
/**
56-
* Get the envelope
57-
* @param {object} req Request obj
58-
* @param {object} res Response obj
59-
*/
60-
eg007EnvelopeGetDoc.createController = async (req, res) => {
61-
// Step 1. Check the token
62-
// At this point we should have a good token. But we
63-
// double-check here to enable a better UX to the user.
64-
let tokenOK = req.dsAuthCodeGrant.checkToken(minimumBufferMin);
65-
if (! tokenOK) {
66-
req.flash('info', 'Sorry, you need to re-authenticate.');
67-
// We could store the parameters of the requested operation
68-
// so it could be restarted automatically.
69-
// But since it should be rare to have a token issue here,
70-
// we'll make the user re-enter the form data after
71-
// authentication.
72-
req.dsAuthCodeGrant.setEg(req, eg);
73-
res.redirect(mustAuthenticate);
74-
}
75-
let envelopeDocuments = req.session.envelopeDocuments;
76-
if (! req.session.envelopeId || ! envelopeDocuments ) {
77-
res.render('pages/examples/eg007EnvelopeGetDoc', {
78-
csrfToken: req.csrfToken(),
79-
title: "Download a document",
80-
envelopeOk: req.session.envelopeId,
81-
documentsOk: envelopeDocuments,
82-
sourceFile: path.basename(__filename),
83-
sourceUrl: dsConfig.githubExampleUrl + path.basename(__filename),
84-
documentation: dsConfig.documentation + eg,
85-
showDoc: dsConfig.documentation
86-
});
87-
}
88-
89-
// Step 2. Call the worker method
90-
let accountId = req.dsAuthCodeGrant.getAccountId()
91-
, dsAPIclient = req.dsAuthCodeGrant.getDSApi()
92-
// Additional data validation might also be appropriate
93-
, documentId = validator.escape(req.body.docSelect)
94-
, args = {
95-
dsAPIclient: dsAPIclient,
96-
accountId: accountId,
97-
documentId: documentId,
98-
envelopeDocuments: envelopeDocuments
1+
"""007: Get an envelope's document"""
2+
3+
from flask import render_template, url_for, redirect, session, flash, request, send_file
4+
from os import path
5+
import json
6+
import re
7+
import io
8+
from app import app, ds_config, views
9+
from docusign_esign import *
10+
from docusign_esign.rest import ApiException
11+
12+
eg = "eg007" # reference (and url) for this example
13+
14+
def controller():
15+
"""Controller router using the HTTP method"""
16+
if request.method == 'GET':
17+
return get_controller()
18+
elif request.method == 'POST':
19+
return create_controller()
20+
else:
21+
return render_template('404.html'), 404
22+
23+
24+
def create_controller():
25+
"""
26+
1. Check the token
27+
2. Call the worker method
28+
3. Show results
29+
"""
30+
minimum_buffer_min = 3
31+
token_ok = views.ds_token_ok(minimum_buffer_min)
32+
if token_ok and 'envelope_id' in session and 'envelope_documents' in session:
33+
# 2. Call the worker method
34+
# More data validation would be a good idea here
35+
# Strip anything other than characters listed
36+
pattern = re.compile('([^\w \-\@\.\,])+')
37+
document_id = pattern.sub('', request.form.get('document_id'))
38+
39+
args = {
40+
'account_id': session['ds_account_id'],
41+
'envelope_id': session['envelope_id'],
42+
'base_path': session['ds_base_path'],
43+
'ds_access_token': session['ds_access_token'],
44+
'document_id': document_id,
45+
'envelope_documents': session['envelope_documents']
9946
}
100-
, results = null
101-
;
102-
103-
try {
104-
results = await eg007EnvelopeGetDoc.worker (args)
105-
}
106-
catch (error) {
107-
let errorBody = error && error.response && error.response.body
108-
// we can pull the DocuSign error code and message from the response body
109-
, errorCode = errorBody && errorBody.errorCode
110-
, errorMessage = errorBody && errorBody.message
111-
;
112-
// In production, may want to provide customized error messages and
113-
// remediation advice to the user.
114-
res.render('pages/error', {err: error, errorCode: errorCode, errorMessage: errorMessage});
115-
}
116-
if (results) {
117-
// ***DS.snippet.2.start
118-
res.writeHead(200, {
119-
'Content-Type': results.mimetype,
120-
'Content-disposition': 'inline;filename=' + results.docName,
121-
'Content-Length': results.fileBytes.length
122-
});
123-
res.end(results.fileBytes, 'binary');
124-
// ***DS.snippet.2.end
125-
}
126-
}
127-
128-
/**
129-
* This function does the work of listing the envelope's recipients
130-
* @param {object} args An object with the following elements: <br/>
131-
* <tt>dsAPIclient</tt>: The DocuSign API Client object, already set with an access token and base url <br/>
132-
* <tt>accountId</tt>: Current account Id <br/>
133-
* <tt>documentId</tt>: the document to be fetched <br/>
134-
* <tt>envelopeDocuments</tt>: object with data about the envelope's documents
135-
*/
136-
// ***DS.worker.start ***DS.snippet.1.start
137-
eg007EnvelopeGetDoc.worker = async (args) => {
138-
let envelopesApi = new docusign.EnvelopesApi(args.dsAPIclient)
139-
, getEnvelopeDocumentP = promisify(envelopesApi.getDocument).bind(envelopesApi)
140-
, results = null
141-
;
142-
143-
// Step 1. EnvelopeDocuments::get.
144-
// Exceptions will be caught by the calling function
145-
results = await getEnvelopeDocumentP(
146-
args.accountId, args.envelopeDocuments.envelopeId, args.documentId, null);
147-
148-
let docItem = args.envelopeDocuments.documents.find(item => item.documentId === args.documentId)
149-
, docName = docItem.name
150-
, hasPDFsuffix = docName.substr(docName.length - 4).toUpperCase() === '.PDF'
151-
, pdfFile = hasPDFsuffix
152-
;
153-
// Add .pdf if it's a content or summary doc and doesn't already end in .pdf
154-
if ((docItem.type === "content" || docItem.type === "summary") && !hasPDFsuffix){
155-
docName += ".pdf";
156-
pdfFile = true;
157-
}
158-
// Add .zip as appropriate
159-
if (docItem.type === "zip") {
160-
docName += ".zip"
161-
}
162-
163-
// Return the file information
164-
// See https://stackoverflow.com/a/30625085/64904
165-
let mimetype;
166-
if (pdfFile) {
47+
48+
try:
49+
results = worker(args)
50+
except ApiException as err:
51+
error_body_json = err and hasattr(err, 'body') and err.body
52+
# we can pull the DocuSign error code and message from the response body
53+
error_body = json.loads(error_body_json)
54+
error_code = error_body and 'errorCode' in error_body and error_body['errorCode']
55+
error_message = error_body and 'message' in error_body and error_body['message']
56+
# In production, may want to provide customized error messages and
57+
# remediation advice to the user.
58+
return render_template('error.html',
59+
err=err,
60+
error_code=error_code,
61+
error_message=error_message
62+
)
63+
64+
return send_file(
65+
results["data"],
66+
mimetype=results["mimetype"],
67+
as_attachment=True,
68+
attachment_filename=results["doc_name"]
69+
)
70+
71+
elif not token_ok:
72+
flash('Sorry, you need to re-authenticate.')
73+
# We could store the parameters of the requested operation
74+
# so it could be restarted automatically.
75+
# But since it should be rare to have a token issue here,
76+
# we'll make the user re-enter the form data after
77+
# authentication.
78+
session['eg'] = url_for(eg)
79+
return redirect(url_for('ds_must_authenticate'))
80+
elif not 'envelope_id' in session or not 'envelope_documents' in session:
81+
return render_template("eg007_envelope_get_doc.html",
82+
title="Download an Envelope's Document",
83+
envelope_ok=False,
84+
documents_ok=False,
85+
source_file=path.basename(__file__),
86+
source_url=ds_config.DS_CONFIG['github_example_url'] + path.basename(__file__),
87+
documentation=ds_config.DS_CONFIG['documentation'] + eg,
88+
show_doc=ds_config.DS_CONFIG['documentation'],
89+
)
90+
91+
92+
def worker(args):
93+
"""
94+
1. Call the envelope get method
95+
"""
96+
97+
# Exceptions will be caught by the calling function
98+
api_client = ApiClient()
99+
api_client.host = args['base_path']
100+
api_client.set_default_header("Authorization", "Bearer " + args['ds_access_token'])
101+
envelope_api = EnvelopesApi(api_client)
102+
document_id = args['document_id']
103+
104+
# The SDK always stores the received file as a temp file
105+
temp_file = envelope_api.get_document(args['account_id'], document_id, args['envelope_id'])
106+
doc_item = next(item for item in args['envelope_documents']['documents'] if item['document_id'] == document_id)
107+
doc_name = doc_item['name']
108+
has_pdf_suffix = doc_name[-4:].upper() == '.PDF'
109+
pdf_file = has_pdf_suffix
110+
# Add .pdf if it's a content or summary doc and doesn't already end in .pdf
111+
if (doc_item["type"] == "content" or doc_item["type"] == "summary") and not has_pdf_suffix:
112+
doc_name += ".pdf"
113+
pdf_file = True
114+
# Add .zip as appropriate
115+
if doc_item["type"] == "zip":
116+
doc_name += ".zip"
117+
118+
# Return the file information
119+
if pdf_file:
167120
mimetype = 'application/pdf'
168-
} else if (docItem.type === 'zip') {
121+
elif doc_item["type"] == 'zip':
169122
mimetype = 'application/zip'
170-
} else {
123+
else:
171124
mimetype = 'application/octet-stream'
172-
}
173125

174-
return ({mimetype: mimetype, docName: docName, fileBytes: results});
175-
}
176-
// ***DS.worker.end ***DS.snippet.1.end
126+
return {'mimetype': mimetype, 'doc_name': doc_name, 'data': temp_file}
127+
128+
129+
# ***DS.worker.end ***DS.snippet.1.end
130+
131+
132+
def get_controller():
133+
"""responds with the form for the example"""
134+
135+
if views.ds_token_ok():
136+
documents_ok = 'envelope_documents' in session
137+
document_options = []
138+
if documents_ok:
139+
# Prepare the select items
140+
envelope_documents = session["envelope_documents"]
141+
document_options = map( lambda item :
142+
{'text': item['name'], 'document_id': item['document_id']}
143+
, envelope_documents['documents'])
144+
145+
return render_template("eg007_envelope_get_doc.html",
146+
title="Download an Envelope's Document",
147+
envelope_ok='envelope_id' in session,
148+
documents_ok=documents_ok,
149+
source_file=path.basename(__file__),
150+
source_url=ds_config.DS_CONFIG['github_example_url'] + path.basename(__file__),
151+
documentation=ds_config.DS_CONFIG['documentation'] + eg,
152+
show_doc=ds_config.DS_CONFIG['documentation'],
153+
document_options=document_options
154+
)
155+
else:
156+
# Save the current operation so it will be resumed after authentication
157+
session['eg'] = url_for(eg)
158+
return redirect(url_for('ds_must_authenticate'))
177159

app/templates/eg007_envelope_get_doc.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ <h4>7. Download a document from an envelope</h4>
2424
View source file <a target="_blank" href="{{ source_url | safe }}">{{ source_file }}</a> on GitHub.
2525
</p>
2626

27-
<% if (! envelope_ok) { %>
27+
{% if not envelope_ok %}
2828
<p>Problem: please first create an envelope using <a href="eg002">example 2.</a> <br/>
2929
You will then need to use example 6 to create the list of documents.<br/>
3030
Thank you.</p>
3131

3232
<form class="eg" action="eg002" method="get">
3333
<button type="submit" class="btn btn-primary">Continue</button>
3434
</form>
35-
<% } else if (!documentsOk) { %>
35+
{% elif not documents_ok %}
3636
<p>Problem: please first create a list of the envelope's documents using
3737
<a href="eg006">example 6.</a> <br/>
3838
Thank you.</p>
@@ -47,11 +47,11 @@ <h4>7. Download a document from an envelope</h4>
4747
<form class="eg" action="" method="post" data-busy="form-download">
4848
<div class="form-group">
4949
<label for="docSelect">Document selection</label>
50-
<select class="custom-select" id="docSelect"
51-
name="docSelect" aria-describedby="emailHelp">
52-
<% for (let opt of documentOptions) { %>
53-
<option value="<%= opt.documentId %>"><%= opt.text %></option>
54-
{% endif %}
50+
<select class="custom-select" id="document_id"
51+
name="document_id" aria-describedby="emailHelp">
52+
{% for opt in document_options %}
53+
<option value="{{ opt.document_id | safe }}">{{ opt.text }}</option>
54+
{% endfor %}
5555
</select>
5656
</div>
5757
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>

app/views.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import uuid
88
from app import app, ds_config, eg001_embedded_signing, eg002_signing_via_email, \
99
eg003_list_envelopes, eg004_envelope_info, eg005_envelope_recipients, \
10-
eg006_envelope_docs
10+
eg006_envelope_docs, eg007_envelope_get_doc
1111

1212

1313
@app.route('/')
@@ -55,6 +55,11 @@ def eg006():
5555
return eg006_envelope_docs.controller()
5656

5757

58+
@app.route('/eg007', methods=['GET', 'POST'])
59+
def eg007():
60+
return eg007_envelope_get_doc.controller()
61+
62+
5863
@app.route('/ds_return')
5964
def ds_return():
6065
event = request.args.get('event')

0 commit comments

Comments
 (0)