jhpyle / docassemble

A free, open-source expert system for guided interviews and document assembly, based on Python, YAML, and Markdown.
https://docassemble.org
MIT License
796 stars 258 forks source link

UnboundLocalError: local variable 'field' referenced before assignment #329

Closed cschwarz007 closed 4 years ago

cschwarz007 commented 4 years ago

I am trying to achieve the following:

So far, so good. I am trying to do this, using the following code (key excerpts only):

---
objects:
  - user: Individual
  - BDF_company: DAList.using(
      object_type=Person,
      there_are_any=True)
  - BDF_company[i].DocuSignInfo: DAList.using(
      object_type=DAEmailRecipient,
      complete_attribute='complete')
  - Other_party: DAList.using(
      object_type=Person,
      there_are_any=True)
  - Other_party[i].DocuSignInfo: DAList.using(
      object_type=DAEmailRecipient,
      complete_attribute='complete')  
  - my_attachments: DADict 
---
mandatory: way_of_signing == "use_DocuSign"
question: Your Document
needs: Other_party[0].DocuSignInfo[0].name
subquestion: |
  Press continue to send it for signatures.
field: doc_review
attachments:
  name: NDA ready for DocuSign
  filename: ${ my_attachment_filename }
  variable name: my_attachments_docusign[i]
  docx template file:
    code: |
      template_file  
  language: ${ doc_lang }
---
mandatory: way_of_signing == "use_DocuSign"
sets:
  - Other_party[0].DocuSignInfo[0].name
decoration: file-signature
question: |
  Please enter signatories names and e-mail addresses
fields:
  code: Signatories
---
code: |
  Signatories = list()
  for index in range(BDF_company.number()):
    Signatories.append(
      {"note": "** " + BDF_company[index].name.text + " **"
      })
    Signatories.append(          
      {"label": '1<sup>st</sup> signatory name',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '1<sup>st</sup> signatory e-mail',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory name',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory e-mail',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
  for index in range(Other_party.number()):
    Signatories.append(
      {"note": "** " + Other_party[index].name.text + " **"
      })  
    Signatories.append(
      {"label": '1<sup>st</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '1<sup>st</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })      
---

Albeit deperiated, I am using defined() in the DOCX template. The reason being that I present the template before even asking for the way of signing (which can be paper based which leads to exiting the interview of DocuSign - see code above) for the user to validate the contract content. At this point in time, the variable way_of_signing is not (yet) defined and hence the code does not insert the DocuSign anchors. Here is the relevant code from the DOCX template:

{% if defined(‘way_of_signing’) %}{{ generate_anchor('signHere', Other_party[0].DocuSignInfo[0].address) }}{% endif %}
Name: {% if defined(‘way_of_signing’) %}{{ generate_anchor('fullName', Other_party[0].DocuSignInfo[0].address) }}{% endif %}
Title:
Date: {% if defined(‘way_of_signing’) %}{{ generate_anchor('date', Other_party[0].DocuSignInfo[0].address) }}{% endif %}

{% if defined(‘way_of_signing’) %}{{ generate_anchor('signHere', Other_party[0].DocuSignInfo[1].address) }}{% endif %}
Name: {% if defined(‘way_of_signing’) %}{{ generate_anchor('fullName', Other_party[0].DocuSignInfo[1].address) }}{% endif %}
Title:
Date: {% if defined(‘way_of_signing’) %}{{ generate_anchor(' date ', Other_party[0].DocuSignInfo[1].address) }}{% endif %}

The issue now is that interview never asks for Other_party[0].DocuSignInfo[0/1].address, but the question Please enter signatories names and e-mail addresses shows no input fields at all and I get the following error as result of the interview:

Traceback (most recent call last):
  File "/usr/share/docassemble/local3.6/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/share/docassemble/local3.6/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/share/docassemble/local3.6/lib/python3.6/site-packages/docassemble/webapp/server.py", line 6095, in index
    field_info = interview_status.get_field_info()
  File "/usr/share/docassemble/local3.6/lib/python3.6/site-packages/docassemble/base/parse.py", line 509, in get_field_info
    if field.datatype == 'object_checkboxes':
UnboundLocalError: local variable 'field' referenced before assignment

Clearly, the issue is on my end. Can anyone help me think in the right direction? Many thanks!

jhpyle commented 4 years ago

This happened because Signatories was an empty list, which it shouldn't be.

You may not have intended Signatories to be empty, but it might have ended up empty because of the way that docassemble looks for code blocks to define variables.

You should do something like the following to make sure that your code block always runs until the end. If the code block that defines Signatories needs to stop and fetch a value, it's not going to resume because Signatories is now defined and docassemble will not have any incentive to run the code block again.

need: Signatories_gathered
mandatory: way_of_signing == "use_DocuSign"
sets:
  - Other_party[0].DocuSignInfo[0].name
decoration: file-signature
question: |
  Please enter signatories names and e-mail addresses
fields:
  code: Signatories
---
code: |
  Signatories = list()
  for index in range(BDF_company.number()):
    Signatories.append(
      {"note": "** " + BDF_company[index].name.text + " **"
      })
    Signatories.append(          
      {"label": '1<sup>st</sup> signatory name',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '1<sup>st</sup> signatory e-mail',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory name',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory e-mail',
       "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
  for index in range(Other_party.number()):
    Signatories.append(
      {"note": "** " + Other_party[index].name.text + " **"
      })  
    Signatories.append(
      {"label": '1<sup>st</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '1<sup>st</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
  Signatories_gathered = True     
cschwarz007 commented 4 years ago

Jonathan, many thanks - it works now like a charm. One more issue though I am having. Sometimes only one party signes (BDF_company or Other_party). I hence have adjusted the above code slighty:

code: |
  Signatories = list()
  if disclosing_info_direction != 'BDF':
    for index in range(BDF_company.number()):
      Signatories.append(
        {"note": "** " + BDF_company[index].name.text + " **"
        })
      Signatories.append(          
        {"label": '1<sup>st</sup> signatory name',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].name",
         "note": '<i class="fas fa-user-edit"></i>'
        })
      Signatories.append(
        {"label": '1<sup>st</sup> signatory title',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].title",
         "note": '<i class="fas fa-address-card"></i>'
        })         
      Signatories.append(
        {"label": '1<sup>st</sup> signatory e-mail',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[0].address",
         'datatype': 'email',
         "note": '<i class="fas fa-envelope-open-text"></i>'
        })
      Signatories.append(
        {"label": '2<sup>nd</sup> signatory name',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].name",
         "note": '<i class="fas fa-user-edit"></i>'
        })
      Signatories.append(
        {"label": '2<sup>nd</sup> signatory title',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].title",
         "note": '<i class="fas fa-address-card"></i>'
        })           
      Signatories.append(
        {"label": '2<sup>nd</sup> signatory e-mail',
         "field": "BDF_company[" + str(index) + "].DocuSignInfo[1].address",
         'datatype': 'email',
         "note": '<i class="fas fa-envelope-open-text"></i>'
        })
  for index in range(Other_party.number()):
    Signatories.append(
      {"note": "** " + Other_party[index].name.text + " **"
      })  
    Signatories.append(
      {"label": '1<sup>st</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '1<sup>st</sup> signatory title',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].title",
       "note": '<i class="fas fa-address-card"></i>'
      })      
    Signatories.append(
      {"label": '1<sup>st</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[0].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory name',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].name",
       "note": '<i class="fas fa-user-edit"></i>'
      })
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory title',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].title",
       "note": '<i class="fas fa-address-card"></i>'
      })        
    Signatories.append(
      {"label": '2<sup>nd</sup> signatory e-mail',
       "field": "Other_party[" + str(index) + "].DocuSignInfo[1].address",
       'datatype': 'email',
       "note": '<i class="fas fa-envelope-open-text"></i>'
      })
  Signatories_gathered = True 
---

When I now want to check later whether entries exist, e. g. for BDF_company, I tried using

  for bdf in BDF_company:
    if bdf.DocuSignInfo.number() > 0:

But get an error message: Interview has an error. There was a reference to a variable 'BDF_company[0].DocuSignInfo.there_are_any' that could not be looked up in the question file (for language 'en') or in any of the files incorporated by reference into the question file.

Where is my fault?

jhpyle commented 4 years ago

BDF_company[0].DocuSignInfo is a DAList, and your code that calls .number() needs to know how many items are in the list. So when you call .number(), docassemble starts the standard list gathering mechanism described in https://docassemble.org/docs/groups.html. This process starts by seeking the definition of .there_are_any. Once the list is gathered, docassemble will know how many items are in the fully gathered list and your call to .number() will return a number.

If you are bypassing the standard list gathering mechanism (which you seem to be doing with your fields -> code thing), you may want to call .number_gathered() instead of .number(), because .number_gathered() will not have the side effect of triggering the list gathering process. You can also access the pure Python list by calling len(bdf.DocuSignInfo.elements) and that also will not have any list-gathering side effects.

cschwarz007 commented 4 years ago

Got it - I am using .number_gathered now and am setting .there_is_another and .complete via code...