|
| 1 | +""" Example 002: Remote signer, cc, envelope has three documents """ |
| 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 | +import re |
| 8 | +import json |
| 9 | +from docusign_esign import * |
| 10 | +from docusign_esign.rest import ApiException |
| 11 | + |
| 12 | +eg = "eg002" # reference (and url) for this example |
| 13 | +demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), 'static/demo_documents')) |
| 14 | + |
| 15 | + |
| 16 | +def controller(): |
| 17 | + """Controller router using the HTTP method""" |
| 18 | + if request.method == 'GET': |
| 19 | + return get_controller() |
| 20 | + elif request.method == 'POST': |
| 21 | + return create_controller() |
| 22 | + else: |
| 23 | + return render_template('404.html'), 404 |
| 24 | + |
| 25 | + |
| 26 | +def create_controller(): |
| 27 | + """ |
| 28 | + 1. Check the token |
| 29 | + 2. Call the worker method |
| 30 | + """ |
| 31 | + minimum_buffer_min = 3 |
| 32 | + if views.ds_token_ok(minimum_buffer_min): |
| 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 | + signer_email = pattern.sub('', request.form.get('signer_email')) |
| 38 | + signer_name = pattern.sub('', request.form.get('signer_name')) |
| 39 | + cc_email = pattern.sub('', request.form.get('cc_email')) |
| 40 | + cc_name = pattern.sub('', request.form.get('cc_name')) |
| 41 | + envelope_args = { |
| 42 | + 'signer_email': signer_email, |
| 43 | + 'signer_name': signer_name, |
| 44 | + 'cc_email': cc_email, |
| 45 | + 'cc_name': cc_name, |
| 46 | + 'status': 'sent', |
| 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 ApiException as err: |
| 58 | + error_body_json = err and hasattr(err, 'body') and err.body |
| 59 | + # we can pull the DocuSign error code and message from the response body |
| 60 | + error_body = json.loads(error_body_json) |
| 61 | + error_code = error_body and 'errorCode' in error_body and error_body['errorCode'] |
| 62 | + error_message = error_body and 'message' in error_body and error_body['message'] |
| 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=err, |
| 67 | + error_code=error_code, |
| 68 | + error_message=error_message |
| 69 | + ) |
| 70 | + if results: |
| 71 | + session["envelope_id"] = results["envelope_id"] # Save for use by other examples |
| 72 | + # which need an envelopeId |
| 73 | + return render_template('example_done.html', |
| 74 | + title="Envelope sent", |
| 75 | + h1="Envelope sent", |
| 76 | + message=f"""The envelope has been created and sent!<br/> |
| 77 | + Envelope ID {results["envelope_id"]}.""" |
| 78 | + ) |
| 79 | + |
| 80 | + else: |
| 81 | + flash('Sorry, you need to re-authenticate.') |
| 82 | + # We could store the parameters of the requested operation |
| 83 | + # so it could be restarted automatically. |
| 84 | + # But since it should be rare to have a token issue here, |
| 85 | + # we'll make the user re-enter the form data after |
| 86 | + # authentication. |
| 87 | + session['eg'] = url_for(eg) |
| 88 | + return redirect(url_for('ds_must_authenticate')) |
| 89 | + |
| 90 | + |
| 91 | +def worker(args): |
| 92 | + """ |
| 93 | + 1. Create the envelope request object |
| 94 | + 2. Send the envelope |
| 95 | + """ |
| 96 | + envelope_args = args["envelope_args"] |
| 97 | + # 1. Create the envelope request object |
| 98 | + envelope_definition = make_envelope(envelope_args) |
| 99 | + |
| 100 | + # 2. call Envelopes::create API method |
| 101 | + # Exceptions will be caught by the calling function |
| 102 | + api_client = ApiClient() |
| 103 | + api_client.host = args['base_path'] |
| 104 | + api_client.set_default_header("Authorization", "Bearer " + args['ds_access_token']) |
| 105 | + |
| 106 | + envelope_api = EnvelopesApi(api_client) |
| 107 | + results = envelope_api.create_envelope(args['account_id'], envelope_definition=envelope_definition) |
| 108 | + |
| 109 | + envelope_id = results.envelope_id |
| 110 | + app.logger.info(f'Envelope was created. EnvelopeId {envelope_id}') |
| 111 | + |
| 112 | + return {'envelope_id': envelope_id} |
| 113 | + |
| 114 | + |
| 115 | +# ***DS.worker.end ***DS.snippet.1.end |
| 116 | + |
| 117 | + |
| 118 | +# ***DS.snippet.2.start |
| 119 | +def make_envelope(args): |
| 120 | + """ |
| 121 | + Creates envelope |
| 122 | + Document 1: An HTML document. |
| 123 | + Document 2: A Word .docx document. |
| 124 | + Document 3: A PDF document. |
| 125 | + DocuSign will convert all of the documents to the PDF format. |
| 126 | + The recipients' field tags are placed using <b>anchor</b> strings. |
| 127 | + """ |
| 128 | + |
| 129 | + # document 1 (html) has sign here anchor tag **signature_1** |
| 130 | + # document 2 (docx) has sign here anchor tag /sn1/ |
| 131 | + # document 3 (pdf) has sign here anchor tag /sn1/ |
| 132 | + # |
| 133 | + # The envelope has two recipients. |
| 134 | + # recipient 1 - signer |
| 135 | + # recipient 2 - cc |
| 136 | + # The envelope will be sent first to the signer. |
| 137 | + # After it is signed, a copy is sent to the cc person. |
| 138 | + |
| 139 | + # create the envelope definition |
| 140 | + env = EnvelopeDefinition( |
| 141 | + email_subject='Please sign this document set' |
| 142 | + ) |
| 143 | + doc1_b64 = base64.b64encode(bytes(create_document1(args), 'utf-8')).decode('ascii') |
| 144 | + # read files 2 and 3 from a local directory |
| 145 | + # The reads could raise an exception if the file is not available! |
| 146 | + with open(path.join(demo_docs_path, ds_config.DS_CONFIG['doc_docx']), "rb") as file: |
| 147 | + doc2_docx_bytes = file.read() |
| 148 | + doc2_b64 = base64.b64encode(doc2_docx_bytes).decode('ascii') |
| 149 | + with open(path.join(demo_docs_path, ds_config.DS_CONFIG['doc_pdf']), "rb") as file: |
| 150 | + doc3_pdf_bytes = file.read() |
| 151 | + doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode('ascii') |
| 152 | + |
| 153 | + # Create the document models |
| 154 | + document1 = Document( # create the DocuSign document object |
| 155 | + document_base64=doc1_b64, |
| 156 | + name='Order acknowledgement', # can be different from actual file name |
| 157 | + file_extension='html', # many different document types are accepted |
| 158 | + document_id='1' # a label used to reference the doc |
| 159 | + ) |
| 160 | + document2 = Document( # create the DocuSign document object |
| 161 | + document_base64=doc2_b64, |
| 162 | + name='Battle Plan', # can be different from actual file name |
| 163 | + file_extension='docx', # many different document types are accepted |
| 164 | + document_id='2' # a label used to reference the doc |
| 165 | + ) |
| 166 | + document3 = Document( # create the DocuSign document object |
| 167 | + document_base64=doc3_b64, |
| 168 | + name='Lorem Ipsum', # can be different from actual file name |
| 169 | + file_extension='pdf', # many different document types are accepted |
| 170 | + document_id='3' # a label used to reference the doc |
| 171 | + ) |
| 172 | + # The order in the docs array determines the order in the envelope |
| 173 | + env.documents = [document1, document2, document3] |
| 174 | + |
| 175 | + |
| 176 | + # Create the signer recipient model |
| 177 | + signer1 = Signer( |
| 178 | + email=args['signer_email'], name=args['signer_name'], |
| 179 | + recipient_id="1", routing_order="1" |
| 180 | + ) |
| 181 | + # routingOrder (lower means earlier) determines the order of deliveries |
| 182 | + # to the recipients. Parallel routing order is supported by using the |
| 183 | + # same integer as the order for two or more recipients. |
| 184 | + |
| 185 | + # create a cc recipient to receive a copy of the documents |
| 186 | + cc1 = CarbonCopy( |
| 187 | + email=args['cc_email'], name=args['cc_name'], |
| 188 | + recipient_id="2", routing_order="2") |
| 189 | + |
| 190 | + # Create signHere fields (also known as tabs) on the documents, |
| 191 | + # We're using anchor (autoPlace) positioning |
| 192 | + # |
| 193 | + # The DocuSign platform searches throughout your envelope's |
| 194 | + # documents for matching anchor strings. So the |
| 195 | + # signHere2 tab will be used in both document 2 and 3 since they |
| 196 | + # use the same anchor string for their "signer 1" tabs. |
| 197 | + sign_here1 = SignHere( |
| 198 | + anchor_string = '**signature_1**', anchor_units = 'pixels', |
| 199 | + anchor_y_offset = '10', anchor_x_offset = '20') |
| 200 | + sign_here2 = SignHere( |
| 201 | + anchor_string = '/sn1/', anchor_units = 'pixels', |
| 202 | + anchor_y_offset = '10', anchor_x_offset = '20') |
| 203 | + |
| 204 | + # Add the tabs model (including the sign_here tabs) to the signer |
| 205 | + # The Tabs object wants arrays of the different field/tab types |
| 206 | + signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2]) |
| 207 | + |
| 208 | + # Add the recipients to the envelope object |
| 209 | + recipients = Recipients(signers=[signer1], carbon_copies=[cc1]) |
| 210 | + env.recipients = recipients |
| 211 | + |
| 212 | + # Request that the envelope be sent by setting |status| to "sent". |
| 213 | + # To request that the envelope be created as a draft, set to "created" |
| 214 | + env.status = args["status"] |
| 215 | + |
| 216 | + return env |
| 217 | + |
| 218 | + |
| 219 | +# ***DS.snippet.3.start |
| 220 | +def create_document1(args): |
| 221 | + """ Creates document 1 -- an html document""" |
| 222 | + |
| 223 | + return f""" |
| 224 | + <!DOCTYPE html> |
| 225 | + <html> |
| 226 | + <head> |
| 227 | + <meta charset="UTF-8"> |
| 228 | + </head> |
| 229 | + <body style="font-family:sans-serif;margin-left:2em;"> |
| 230 | + <h1 style="font-family: 'Trebuchet MS', Helvetica, sans-serif; |
| 231 | + color: darkblue;margin-bottom: 0;">World Wide Corp</h1> |
| 232 | + <h2 style="font-family: 'Trebuchet MS', Helvetica, sans-serif; |
| 233 | + margin-top: 0px;margin-bottom: 3.5em;font-size: 1em; |
| 234 | + color: darkblue;">Order Processing Division</h2> |
| 235 | + <h4>Ordered by {args['signer_name']}</h4> |
| 236 | + <p style="margin-top:0em; margin-bottom:0em;">Email: {args['signer_email']}</p> |
| 237 | + <p style="margin-top:0em; margin-bottom:0em;">Copy to: {args['cc_name']}, {args['cc_email']}</p> |
| 238 | + <p style="margin-top:3em;"> |
| 239 | + 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. |
| 240 | + </p> |
| 241 | + <!-- Note the anchor tag for the signature field is in white. --> |
| 242 | + <h3 style="margin-top:3em;">Agreed: <span style="color:white;">**signature_1**/</span></h3> |
| 243 | + </body> |
| 244 | + </html> |
| 245 | + """ |
| 246 | + # ***DS.snippet.3.end |
| 247 | + |
| 248 | + |
| 249 | +def get_controller(): |
| 250 | + """responds with the form for the example""" |
| 251 | + |
| 252 | + if views.ds_token_ok(): |
| 253 | + return render_template("eg002_signing_via_email.html", |
| 254 | + title="Signing via email", |
| 255 | + source_file=path.basename(__file__), |
| 256 | + source_url=ds_config.DS_CONFIG['github_example_url'] + path.basename(__file__), |
| 257 | + documentation=ds_config.DS_CONFIG['documentation'] + eg, |
| 258 | + show_doc=ds_config.DS_CONFIG['documentation'], |
| 259 | + signer_name=ds_config.DS_CONFIG['signer_name'], |
| 260 | + signer_email=ds_config.DS_CONFIG['signer_email'] |
| 261 | + ) |
| 262 | + else: |
| 263 | + # Save the current operation so it will be resumed after authentication |
| 264 | + session['eg'] = url_for(eg) |
| 265 | + return redirect(url_for('ds_must_authenticate')) |
| 266 | + |
| 267 | + |
0 commit comments