Skip to content

Commit 2019c8c

Browse files
Document generation code example (#116)
* added example * updated example * update doc gen code example * removing extra fields * adding step comments --------- Co-authored-by: Paige Rossi <paige.rossi@docusign.com>
1 parent 19938ee commit 2019c8c

File tree

8 files changed

+368
-13
lines changed

8 files changed

+368
-13
lines changed

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
app.register_blueprint(esignature_views.eg039)
105105
app.register_blueprint(esignature_views.eg040)
106106
app.register_blueprint(esignature_views.eg041)
107+
app.register_blueprint(esignature_views.eg042)
107108

108109
if "DYNO" in os.environ: # On Heroku?
109110
import logging

app/ds_config_sample.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"doc_pdf": "World_Wide_Corp_lorem.pdf",
2727
"doc_terms_pdf": "Term_Of_Service.pdf",
2828
"doc_txt": "Welcome.txt",
29+
"doc_offer_letter": "Offer_Letter_Demo.docx",
2930
# Payment gateway information is optional
3031
"gateway_account_id": "{DS_PAYMENT_GATEWAY_ID}",
3132
"gateway_name": "stripe",
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import base64
2+
from flask import session, request
3+
from os import path
4+
from docusign_esign import EnvelopesApi, TemplatesApi, EnvelopeDefinition, Document, Signer, SignHere, \
5+
DateSigned, Tabs, Recipients, DocGenFormField, EnvelopeTemplate, TemplateRole, DocGenFormFields, \
6+
DocGenFormFieldRequest, Envelope
7+
8+
from ...consts import demo_docs_path, pattern
9+
from ...ds_config import DS_CONFIG
10+
from ...docusign import create_api_client
11+
12+
13+
class Eg042DocumentGenerationController:
14+
@staticmethod
15+
def get_args():
16+
"""Get request and session arguments"""
17+
envelope_args = {
18+
"candidate_email": pattern.sub("", request.form.get("candidate_email")),
19+
"candidate_name": pattern.sub("", request.form.get("candidate_name")),
20+
"manager_name": pattern.sub("", request.form.get("manager_name")),
21+
"job_title": pattern.sub("", request.form.get("job_title")),
22+
"salary": pattern.sub("", request.form.get("salary")),
23+
"start_date": pattern.sub("", request.form.get("start_date")),
24+
"doc_file": path.join(demo_docs_path, DS_CONFIG["doc_offer_letter"])
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+
"envelope_args": envelope_args
31+
}
32+
return args
33+
34+
@classmethod
35+
def worker(cls, args):
36+
"""
37+
1. Create the template
38+
2. Update template document
39+
3. Update recipient tabs
40+
4. Create draft envelope
41+
5. Get the document id
42+
6. Merge the data fields
43+
7. Send the envelope
44+
"""
45+
46+
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
47+
templates_api = TemplatesApi(api_client)
48+
envelopes_api = EnvelopesApi(api_client)
49+
50+
account_id = args["account_id"]
51+
envelope_args = args["envelope_args"]
52+
53+
# Step 2a start
54+
template_data = cls.make_template()
55+
template = templates_api.create_template(account_id, envelope_template=template_data)
56+
template_id = template.template_id
57+
58+
# Step 2a end
59+
60+
# Update template document
61+
# Step 3a start
62+
document_id = '1'
63+
templates_api.update_document(
64+
account_id, document_id, template_id,
65+
envelope_definition=cls.template_document(envelope_args)
66+
)
67+
# Step 3a end
68+
69+
# Update recipient tabs
70+
# Step 4a start
71+
recipient_id = '1'
72+
templates_api.create_tabs(
73+
account_id, recipient_id, template_id,
74+
template_tabs=cls.recipient_tabs()
75+
)
76+
# Step 4a end
77+
78+
# Create draft envelope
79+
# Step 5a start
80+
envelope_definition = cls.make_envelope(template_id, envelope_args)
81+
envelope = envelopes_api.create_envelope(account_id, envelope_definition=envelope_definition)
82+
envelope_id = envelope.envelope_id
83+
# Step 5a end
84+
85+
# Get the document id
86+
# Step 6 start
87+
doc_gen_form_fields_response = envelopes_api.get_envelope_doc_gen_form_fields(account_id, envelope_id)
88+
document_id_guid = doc_gen_form_fields_response.doc_gen_form_fields[0].document_id
89+
# Step 6 end
90+
91+
# Merge the data fields
92+
# Step 7a start
93+
form_fields = cls.form_fields(envelope_args, document_id_guid)
94+
envelopes_api.update_envelope_doc_gen_form_fields(
95+
account_id,
96+
envelope_id,
97+
doc_gen_form_field_request=form_fields
98+
)
99+
# Step 7a end
100+
101+
# Send the envelope
102+
# Step 8 start
103+
send_envelope_req = Envelope(status="sent")
104+
envelope = envelopes_api.update(account_id, envelope_id, envelope=send_envelope_req)
105+
# Step 8 end
106+
return envelope
107+
108+
# Step 2b start
109+
@classmethod
110+
def make_template(cls):
111+
# Create recipient
112+
signer = Signer(
113+
role_name="signer",
114+
recipient_id="1",
115+
routing_order="1",
116+
)
117+
recipients = Recipients(
118+
signers=[signer]
119+
)
120+
121+
# Create the envelope template model
122+
template_request = EnvelopeTemplate(
123+
name="Example document generation template",
124+
description="Example template created via the API",
125+
email_subject="Please sign this document",
126+
shared="false",
127+
recipients=recipients,
128+
status="created"
129+
)
130+
return template_request
131+
132+
# Step 2b end
133+
134+
# Step 3b start
135+
@classmethod
136+
def template_document(cls, args):
137+
with open(args["doc_file"], "rb") as file:
138+
content_bytes = file.read()
139+
base64_file_content = base64.b64encode(content_bytes).decode("ascii")
140+
141+
# Create the document model
142+
document = Document(
143+
document_base64=base64_file_content,
144+
name="OfferLetterDemo.docx",
145+
file_extension="docx",
146+
document_id=1,
147+
order=1,
148+
pages=1
149+
)
150+
151+
envelope_definition = EnvelopeDefinition(
152+
documents=[document]
153+
)
154+
return envelope_definition
155+
# Step 3b end
156+
157+
# Step 4b start
158+
@classmethod
159+
def recipient_tabs(cls):
160+
# Create tabs
161+
sign_here = SignHere(
162+
anchor_string="Employee Signature",
163+
anchor_units="pixels",
164+
anchor_x_offset="5",
165+
anchor_y_offset="-22"
166+
)
167+
date_signed = DateSigned(
168+
anchor_string="Date",
169+
anchor_units="pixels",
170+
anchor_y_offset="-22"
171+
)
172+
tabs = Tabs(
173+
sign_here_tabs=[sign_here],
174+
date_signed_tabs=[date_signed]
175+
)
176+
return tabs
177+
# Step 4b end
178+
179+
# Step 5b start
180+
@classmethod
181+
def make_envelope(cls, template_id, args):
182+
# Create the signer model
183+
signer = TemplateRole(
184+
email=args["candidate_email"],
185+
name=args["candidate_name"],
186+
role_name="signer"
187+
)
188+
189+
# Create the envelope model
190+
envelope_definition = EnvelopeDefinition(
191+
template_roles=[signer],
192+
status="created",
193+
template_id=template_id
194+
)
195+
return envelope_definition
196+
# Step 5b end
197+
198+
# Step 7b start
199+
@classmethod
200+
def form_fields(cls, args, document_id_guid):
201+
doc_gen_form_field_request = DocGenFormFieldRequest(
202+
doc_gen_form_fields=[
203+
DocGenFormFields(
204+
document_id=document_id_guid,
205+
doc_gen_form_field_list=[
206+
DocGenFormField(
207+
name="Candidate_Name",
208+
value=args["candidate_name"]
209+
),
210+
DocGenFormField(
211+
name="Manager_Name",
212+
value=args["manager_name"]
213+
),
214+
DocGenFormField(
215+
name="Job_Title",
216+
value=args["job_title"]
217+
),
218+
DocGenFormField(
219+
name="Salary",
220+
value=args["salary"]
221+
),
222+
DocGenFormField(
223+
name="Start_Date",
224+
value=args["start_date"]
225+
)
226+
]
227+
)
228+
]
229+
)
230+
return doc_gen_form_field_request
231+
# Step 7b end

app/eSignature/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@
3838
from .eg039_in_person_signer import eg039
3939
from .eg040_document_visibility import eg040
4040
from .eg041_cfr_embedded_signing import eg041
41+
from .eg042_document_generation import eg042
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
""" Example 002: Remote signer, cc, envelope has three documents """
2+
3+
from os import path
4+
5+
from docusign_esign.client.api_exception import ApiException
6+
from flask import render_template, session, Blueprint, request
7+
8+
from ..examples.eg042_document_generation import Eg042DocumentGenerationController
9+
from ...docusign import authenticate, ensure_manifest, get_example_by_number
10+
from ...ds_config import DS_CONFIG
11+
from ...error_handlers import process_error
12+
from ...consts import pattern, API_TYPE
13+
14+
example_number = 42
15+
api = API_TYPE["ESIGNATURE"]
16+
eg = f"eg0{example_number}" # reference (and url) for this example
17+
eg042 = Blueprint(eg, __name__)
18+
19+
20+
@eg042.route(f"/{eg}", methods=["POST"])
21+
@authenticate(eg=eg, api=api)
22+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
23+
def sign_by_email():
24+
"""
25+
1. Get required arguments
26+
2. Call the worker method
27+
3. Render success response with envelopeId
28+
"""
29+
30+
# 1. Get required arguments
31+
args = Eg042DocumentGenerationController.get_args()
32+
try:
33+
# 1. Call the worker method
34+
results = Eg042DocumentGenerationController.worker(args)
35+
except ApiException as err:
36+
return process_error(err)
37+
38+
# 2. Render success response with envelopeId
39+
example = get_example_by_number(session["manifest"], example_number, api)
40+
return render_template(
41+
"example_done.html",
42+
title=example["ExampleName"],
43+
message=example["ResultsPageText"].format(results.envelope_id)
44+
)
45+
46+
47+
@eg042.route(f"/{eg}", methods=["GET"])
48+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
49+
@authenticate(eg=eg, api=api)
50+
def get_view():
51+
"""responds with the form for the example"""
52+
example = get_example_by_number(session["manifest"], example_number, api)
53+
54+
return render_template(
55+
"eSignature/eg042_document_generation.html",
56+
title=example["ExampleName"],
57+
example=example,
58+
source_file="eg042_document_generation.py",
59+
source_url=DS_CONFIG["github_example_url"] + "eg042_document_generation.py",
60+
documentation=DS_CONFIG["documentation"] + eg,
61+
show_doc=DS_CONFIG["documentation"],
62+
signer_name=DS_CONFIG["signer_name"],
63+
signer_email=DS_CONFIG["signer_email"]
64+
)
36.3 KB
Binary file not shown.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<!-- extend base layout --> {% extends "base.html" %} {% block content %}
2+
3+
{% include 'example_info.html' %}
4+
5+
{% set form_index = 0 %}
6+
{% set candidate_email_index = 0 %}
7+
{% set candidate_name_index = 1 %}
8+
{% set manager_name_index = 2 %}
9+
{% set job_title_index = 3 %}
10+
{% set salary_index = 4 %}
11+
{% set start_date_index = 5 %}
12+
13+
<form class="eg" action="" method="post" data-busy="form">
14+
{% if 'FormName' in example['Forms'][form_index] %}
15+
<p>{{ example['Forms'][form_index]['FormName'] | safe }}</p>
16+
{% endif %}
17+
18+
<div class="form-group">
19+
<label for="candidate_email">{{ example['Forms'][form_index]['Inputs'][candidate_email_index]['InputName'] }}</label>
20+
<input type="email" class="form-control" id="candidate_email" name="candidate_email"
21+
aria-describedby="emailHelp" placeholder="{{ example['Forms'][form_index]['Inputs'][candidate_email_index]['InputPlaceholder'] }}" required
22+
value="{{ candidate_email }}" />
23+
<small id="emailHelp" class="form-text text-muted">{{ session['manifest']['SupportingTexts']['HelpingTexts']['EmailWontBeShared'] | safe}}</small>
24+
</div>
25+
<div class="form-group">
26+
<label for="candidate_name">{{ example['Forms'][form_index]['Inputs'][candidate_name_index]['InputName'] }}</label>
27+
<input type="text" class="form-control" id="candidate_name" placeholder="{{ example['Forms'][form_index]['Inputs'][candidate_name_index]['InputPlaceholder'] }}" name="candidate_name"
28+
value="{{ candidate_name }}" required />
29+
</div>
30+
<div class="form-group">
31+
<label for="manager_name">{{ example['Forms'][form_index]['Inputs'][manager_name_index]['InputName'] }}</label>
32+
<input type="text" class="form-control" id="manager_name" name="manager_name"
33+
aria-describedby="emailHelp" placeholder="{{ example['Forms'][form_index]['Inputs'][manager_name_index]['InputPlaceholder'] }}" required />
34+
</div>
35+
<div class="form-group">
36+
<label for="job_title">{{ example['Forms'][form_index]['Inputs'][job_title_index]['InputName'] }}</label>
37+
<select class="form-control" id="job_title" name="job_title">
38+
<option value="Software Engineer">Software Engineer</option>
39+
<option value="Product Manager">Product Manager</option>
40+
<option value="Sales Representative">Sales Representative</option>
41+
</select>
42+
</div>
43+
<div class="form-group">
44+
<label for="salary">{{ example['Forms'][form_index]['Inputs'][salary_index]['InputName'] }}</label>
45+
<input type="text" class="form-control" id="salary" placeholder="{{ example['Forms'][form_index]['Inputs'][salary_index]['InputPlaceholder'] }}" name="salary"
46+
required />
47+
</div>
48+
<div class="form-group">
49+
<label for="start_date">{{ example['Forms'][form_index]['Inputs'][start_date_index]['InputName'] }}</label>
50+
<input type="date" class="form-control" id="start_date" placeholder="{{ example['Forms'][form_index]['Inputs'][start_date_index]['InputPlaceholder'] }}" name="start_date"
51+
required />
52+
</div>
53+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
54+
{% include 'submit_button.html' %}
55+
</form>
56+
57+
{% endblock %}

0 commit comments

Comments
 (0)