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
768 stars 252 forks source link

mandatory: true with list collect question #472

Closed printfhere closed 2 years ago

printfhere commented 2 years ago

Hello, I'm having trouble understanding this error:

IndexError: list assignment index out of range

Traceback (most recent call last): File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request rv = self.dispatch_request() File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request return self.view_functionsrule.endpoint File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/docassemble/webapp/server.py", line 7625, in index interview.assemble(user_dict, interview_status, old_user_dict, force_question=special_question) File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/docassemble/base/parse.py", line 7920, in assemble raise the_error File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/docassemble/base/parse.py", line 7662, in assemble interview_status.populate(question.ask(user_dict, old_user_dict, 'None', [], None, None)) File "/usr/share/docassemble/local3.8/lib/python3.8/site-packages/docassemble/base/parse.py", line 5410, in ask new_iterators[iterator_index] = str(list_indexno) IndexError: list assignment index out of range

I have this error when I try to put mandatory: true to a question block gathering a list with list collect: true.

Example from the repo where I put the question as mandatory (in my use case I have to for ordering purpose):

metadata:
  title: Collect
  documentation: "https://docassemble.org/docs/groups.html#list collect"
  example start: 3
  example end: 3
---
objects:
  fruit: DAList.using(object_type=Thing, there_are_any=True)
---
mandatory: true
question: |
  Tell me about the fruit.
fields:
  - Fruit name: fruit[i].name.text
  - Number of seeds: fruit[i].seeds
    datatype: number
list collect: True
---
question: |
  Are there any more fruit?
yesno: fruit.there_is_another
---
table: fruit.table
rows: fruit
columns:
  - Fruit Name: row_item.name
  - Seeds: row_item.seeds
edit:
  - name.text
---
mandatory: True
question: |
  Summary of fruit
subquestion: |
  ${ fruit.table }

  ${ fruit.add_action() }

I run DocAssemble version 1.2.100

Thanks for the help

jhpyle commented 2 years ago

You can't use mandatory: True with any question that uses the special variables i or x. i only has meaning when it is given a meaning by docassemble's dependency satisfaction system; e.g., the interview needs fruit[0].name.text but there is no block that defines fruit[0].name.text so it sets i to 0 and looks for a block that defines fruit[i].name.text. If you want to force the gathering of the fruit list, do:

mandatory: True
code: |
  fruit.gather()
printfhere commented 2 years ago

Thank you for your quick reply, I missed the part in the docs where it said that we should not use mandatory: true with a question wich references i !

Indeed I want to trigger the gathering of the list at one specific moment in the interview but I don't manage to do it with the following code:

objects:
  fruit: DAList.using(object_type=DAObject, there_are_any=True, there_is_another=False)
---
mandatory: True
code: |
  fruit.gather()
---
question: Tell me about the fruit.
fields:
  - Fruit name: fruit[i].name
  - Number of seeds: fruit[i].seeds
    datatype: number
list collect: True

I miss something basic in my understanding of the gathering process on the same screen.

jhpyle commented 2 years ago

Here is an example of an interview that conducts the list gathering process at a particular point in the interview (between intro_question and favorite_vegetable):

objects:
  fruit: DAList.using(object_type=Thing, there_are_any=True)
---
question: |
  Welcome to the interview
continue button field: intro_question
---
question: |
  Tell me about the fruit.
fields:
  - Fruit name: fruit[i].name.text
  - Number of seeds: fruit[i].seeds
    datatype: number
list collect: True
---
question: |
  Are there any more fruit?
yesno: fruit.there_is_another
---
question: |
  What is your favorite vegetable?
fields:
  - Vegetable: favorite_vegetable
---
table: fruit.table
rows: fruit
columns:
  - Fruit Name: row_item.name
  - Seeds: row_item.seeds
edit:
  - name.text
---
event: final_screen
question: |
  Summary of fruit
subquestion: |
  ${ fruit.table }

  ${ fruit.add_action() }
---
mandatory: True
code: |
  intro_question
  fruit.gather()
  favorite_vegetable
  final_screen
printfhere commented 2 years ago

Hey, thanks for your help, I will try to adapt this code to my use case. Is it possible to use DAObject instead of Thing ? My users (non developers) find confusing the whole "name.text" thing.

When I replace the object type in your example by DAObject:

objects:
  fruit: DAList.using(object_type=DAObject, there_are_any=True)
---
question: Welcome to the interview
continue button field: intro_question
---
question: Tell me about the fruit.
fields:
  - Fruit name: fruit[i].name
  - Number of seeds: fruit[i].seeds
    datatype: number
list collect: True
---
question: Are there any more fruit?
yesno: fruit.there_is_another
---
question: What is your favorite vegetable?
fields:
  - Vegetable: favorite_vegetable
---
table: fruit.table
rows: fruit
columns:
  - Fruit Name: row_item.name
  - Seeds: row_item.seeds
edit:
  - name
---
event: final_screen
question: |
  Summary of fruit
subquestion: |
  ${ fruit.table }

  ${ fruit.add_action() }
---
mandatory: True
code: |
  intro_question
  fruit.gather()
  favorite_vegetable
  final_screen

It seems the fruit.gather() does not work in this case.

jhpyle commented 2 years ago

Depending on your interview, using a generic DAObject may work, but I don't recommend it because a DAObject has no meaningful textual representation.

See https://docassemble.org/docs/groups.html#list%20of%20objects

If you use a plain DAObject as the object_type, then no questions will be asked; this is because the DAObject is meant to be a “base class,” with no meaningful attributes of its own. Thus, calling str(y) on a plain DAObject will simply return a name based on the variable name; no questions will be asked.

You can use whatever attributes you want for the textual representation if you make a subclass of DAObject and define an __str__() method.

printfhere commented 2 years ago

I'm training non dev users to DocAssemble and I need to find the simplest way for them to create interviews.

I think I will settle on that:

objects:
  fruit: DAList.using(object_type=DAObject, there_are_any=True)
---
question: Welcome to the interview
continue button field: intro_question
---
question: Tell me about the fruit.
fields:
  - Fruit name: fruit[i].name
  - Number of seeds: fruit[i].seeds
list collect: True
---
question: What is your favorite vegetable?
fields:
  - Vegetable: favorite_vegetable
---
table: fruit.table
rows: fruit
columns:
  - Fruit Name: row_item.name
  - Seeds: row_item.seeds
edit:
  - name
---
event: final_screen
question: |
  Summary of fruit
subquestion: |
  ${ fruit.table }

  ${ fruit.add_action() }
---
mandatory: True
code: |
  intro_question
  fruit[0].name
  favorite_vegetable
  final_screen

I will tell them that in order to force the order of the question with list collect, they'll have to reference fruit[0] with one of the attribute they want to fill.

Thanks for your help, feel free to close this.