docassemble.AssemblyLine 4.1.0__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {docassemble_assemblyline-4.1.0/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-4.2.0}/PKG-INFO +1 -1
  2. docassemble_assemblyline-4.2.0/docassemble/AssemblyLine/__init__.py +1 -0
  3. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/al_courts.py +12 -11
  4. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/al_general.py +58 -47
  5. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_document.yml +2 -2
  6. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/aldocument.js +24 -3
  7. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/sessions.py +5 -5
  8. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/test_al_general.py +151 -0
  9. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +1 -1
  10. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/setup.py +1 -1
  11. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/__init__.py +0 -1
  12. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/LICENSE +0 -0
  13. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/MANIFEST.in +0 -0
  14. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/README.md +0 -0
  15. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/al_document.py +0 -0
  16. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/custom_jinja_filters.py +0 -0
  17. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_code.yml +0 -0
  18. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_language.yml +0 -0
  19. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  20. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  21. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +0 -0
  22. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +0 -0
  23. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +0 -0
  24. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +0 -0
  25. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +0 -0
  26. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  27. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +0 -0
  28. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +0 -0
  29. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  30. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +0 -0
  31. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/feedback.yml +0 -0
  32. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +0 -0
  33. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +0 -0
  34. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +0 -0
  35. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/ql_namechange.yml +0 -0
  36. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -0
  37. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  38. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  39. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +0 -0
  40. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +0 -0
  41. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml +0 -0
  42. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  43. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  44. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  45. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_catchall.yml +0 -0
  46. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  47. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_if_final.yml +0 -0
  48. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_preferred_formats.yml +0 -0
  49. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +0 -0
  50. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/questions/test_signature.yml +0 -0
  51. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  52. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/es-words.yml +0 -0
  53. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  54. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  55. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  56. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  57. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  58. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  59. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +0 -0
  60. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  61. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  62. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  63. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  64. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  65. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  66. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  67. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  68. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  69. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  70. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  71. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  72. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  73. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  74. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  75. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  76. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  77. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  78. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  79. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  80. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  81. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  82. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/test_catchall.docx +0 -0
  83. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/data/templates/test_if_final.docx +0 -0
  84. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/language.py +0 -0
  85. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/py.typed +0 -0
  86. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/requirements.txt +0 -0
  87. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/sign.py +0 -0
  88. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/test_al_document.py +0 -0
  89. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/AssemblyLine/test_language.py +0 -0
  90. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble/__init__.py +0 -0
  91. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +0 -0
  92. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  93. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble.AssemblyLine.egg-info/not-zip-safe +0 -0
  94. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble.AssemblyLine.egg-info/requires.txt +0 -0
  95. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/docassemble.AssemblyLine.egg-info/top_level.txt +0 -0
  96. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/pyproject.toml +0 -0
  97. {docassemble_assemblyline-4.1.0 → docassemble_assemblyline-4.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 4.1.0
3
+ Version: 4.2.0
4
4
  Home-page: https://courtformsonline.org
5
5
  Author: Suffolk Legal Innovation and Technology Lab
6
6
  Author-email: qsteenhuis@suffolk.edu
@@ -0,0 +1 @@
1
+ __version__ = '4.2.0'
@@ -3,7 +3,7 @@ Package for a very simple / MVP list of courts that is mostly signature compatib
3
3
  """
4
4
 
5
5
  import os
6
- from typing import Any, Callable, Dict, List, Mapping, Optional, Union, Set
6
+ from typing import Any, Callable, Dict, List, Mapping, Optional, Union, Set, Tuple
7
7
  import pandas as pd
8
8
  import docassemble.base.functions
9
9
  from docassemble.base.util import (
@@ -238,12 +238,12 @@ class ALCourtLoader(DAObject):
238
238
  # Only solution I can think of would require court database owners to assign each court a unique ID
239
239
  # and something that triggers recalculating the court address/etc info.
240
240
 
241
- def all_courts(self) -> List[Dict[int, str]]:
241
+ def all_courts(self) -> List[Tuple[int, str]]:
242
242
  """
243
243
  Return a list of all courts in the spreadsheet.
244
244
 
245
245
  Returns:
246
- List[Dict[int, str]]: List of all ALCourt instances without filtering.
246
+ List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
247
247
  """
248
248
  return self.filter_courts(None)
249
249
 
@@ -329,13 +329,14 @@ class ALCourtLoader(DAObject):
329
329
  display_column: str = "name",
330
330
  search_string: Optional[str] = None,
331
331
  search_columns: Optional[Union[List[str], str]] = None,
332
- ) -> List[Dict[int, str]]:
332
+ ) -> List[Tuple[int, str]]:
333
333
  """
334
334
  Retrieve a list of all courts in the specified county.
335
335
 
336
336
  This function fetches courts suitable for displaying as a drop-down or radio button list
337
- in Docassemble. The results are dictionaries where the key is the index in the dataframe,
338
- useful for retrieving the court's full details later using the as_court() method.
337
+ in Docassemble. The results are tuples where the first element is the dataframe index
338
+ (useful for retrieving the court's full details later using the as_court() method) and
339
+ the second element is the display value from the specified display_column.
339
340
 
340
341
  Args:
341
342
  county_name (str): Name of the county.
@@ -346,7 +347,7 @@ class ALCourtLoader(DAObject):
346
347
  the search_string in a case-insensitive manner. Defaults to None.
347
348
 
348
349
  Returns:
349
- List[Dict[int, str]]: List of dictionaries representing matching courts.
350
+ List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
350
351
  """
351
352
  return self.filter_courts(
352
353
  court_types=county_name,
@@ -363,11 +364,11 @@ class ALCourtLoader(DAObject):
363
364
  display_column: str = "name",
364
365
  search_string: Optional[str] = None,
365
366
  search_columns: Optional[Union[List[str], str]] = None,
366
- ) -> List[Dict[int, str]]:
367
+ ) -> List[Tuple[int, str]]:
367
368
  """
368
- Return a filtered subset of courts represented as a list of dictionaries.
369
+ Return a filtered subset of courts represented as a list of tuples.
369
370
 
370
- Each dictionary has the format {index: name}, where "index" refers to the dataframe index and "name"
371
+ Each tuple has the format (index, display_value), where "index" refers to the dataframe index and "display_value"
371
372
  is determined by the `display_column`.
372
373
 
373
374
  Args:
@@ -380,7 +381,7 @@ class ALCourtLoader(DAObject):
380
381
  the search_string in a case-insensitive manner. Defaults to None.
381
382
 
382
383
  Returns:
383
- List[Dict[int, str]]: List of dictionaries representing filtered courts.
384
+ List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
384
385
  """
385
386
  df = self._load_courts()
386
387
  if court_types:
@@ -37,6 +37,7 @@ from docassemble.base.util import (
37
37
  log,
38
38
  value,
39
39
  )
40
+ from docassemble.ALToolbox.misc import fa_icon
40
41
  import random
41
42
  import re
42
43
  import pycountry
@@ -802,12 +803,12 @@ class ALPeopleList(DAList):
802
803
  [person.familiar(**kwargs) for person in self], and_string=word("or")
803
804
  )
804
805
 
805
- def short_list(self, limit: int, truncate_string: str = ", et. al.") -> str:
806
- """Return a subset of the list, truncated with 'et. al.' if it exceeds a given limit.
806
+ def short_list(self, limit: int, truncate_string: str = ", et al.") -> str:
807
+ """Return a subset of the list, truncated with 'et al.' if it exceeds a given limit.
807
808
 
808
809
  Args:
809
810
  limit (int): The maximum number of items to display before truncating.
810
- truncate_string (str, optional): The string to append when truncating. Defaults to ', et. al.'.
811
+ truncate_string (str, optional): The string to append when truncating. Defaults to ', et al.'.
811
812
 
812
813
  Returns:
813
814
  str: Formatted string of names, truncated if needed.
@@ -1113,6 +1114,7 @@ class ALIndividual(Individual):
1113
1114
  maxlengths: Optional[Dict[str, int]] = None,
1114
1115
  suffix_choices: Optional[Union[List[str], Callable]] = None,
1115
1116
  title_options: Optional[Union[List[str], Callable]] = None,
1117
+ required: Optional[Dict[str, bool]] = None,
1116
1118
  ) -> List[Dict[str, str]]:
1117
1119
  """
1118
1120
  Generates suitable field prompts for a name based on the type of entity (person or business)
@@ -1132,6 +1134,7 @@ class ALIndividual(Individual):
1132
1134
  maxlengths (Dict[str, int], optional): A dictionary of field names and their maximum lengths. Default is None.
1133
1135
  suffix_choices (Union[List[str], Callable], optional): A list of suffix options or a callable to generate suffix options, or overridden by value of global `al_name_suffixes`.
1134
1136
  title_options: (Union[List[str], Callable], optional): Deprecated parameter, use `title_choices` instead. If provided, it will be used to set the title choices.
1137
+ required (Dict[str, bool], optional): A dictionary of field names and if they should be required. Default is None.
1135
1138
 
1136
1139
  Returns:
1137
1140
  List[Dict[str, str]]: A list of dictionaries where each dictionary contains field prompt details.
@@ -1195,6 +1198,17 @@ class ALIndividual(Individual):
1195
1198
  if show_if:
1196
1199
  for field in fields:
1197
1200
  field["show if"] = show_if
1201
+
1202
+ if maxlengths:
1203
+ for field in fields:
1204
+ if field["field"] in maxlengths:
1205
+ field["maxlength"] = maxlengths[field["field"]]
1206
+
1207
+ if required:
1208
+ for field in fields:
1209
+ if field["field"] in required:
1210
+ field["required"] = required[field["field"]]
1211
+
1198
1212
  return fields
1199
1213
  elif person_or_business == "business":
1200
1214
  fields = [
@@ -1205,6 +1219,17 @@ class ALIndividual(Individual):
1205
1219
  ]
1206
1220
  if show_if:
1207
1221
  fields[0]["show if"] = show_if
1222
+
1223
+ if maxlengths:
1224
+ for field in fields:
1225
+ if field["field"] in maxlengths:
1226
+ field["maxlength"] = maxlengths[field["field"]]
1227
+
1228
+ if required:
1229
+ for field in fields:
1230
+ if field["field"] in required:
1231
+ field["required"] = required[field["field"]]
1232
+
1208
1233
  return fields
1209
1234
  else:
1210
1235
  # Note: the labels are template block objects: if they are keys,
@@ -1272,6 +1297,12 @@ class ALIndividual(Individual):
1272
1297
  for field in fields:
1273
1298
  if field["field"] in maxlengths:
1274
1299
  field["maxlength"] = maxlengths[field["field"]]
1300
+
1301
+ if required:
1302
+ for field in fields:
1303
+ if field["field"] in required:
1304
+ field["required"] = required[field["field"]]
1305
+
1275
1306
  return fields
1276
1307
 
1277
1308
  def address_fields(
@@ -1323,6 +1354,7 @@ class ALIndividual(Individual):
1323
1354
  show_if: Union[str, Dict[str, str], None] = None,
1324
1355
  maxlengths: Optional[Dict[str, int]] = None,
1325
1356
  choices: Optional[Union[List[Dict[str, str]], Callable]] = None,
1357
+ required: Optional[Dict[str, bool]] = None,
1326
1358
  ) -> List[Dict[str, str]]:
1327
1359
  """
1328
1360
  Generate fields for capturing gender information, including a
@@ -1333,6 +1365,7 @@ class ALIndividual(Individual):
1333
1365
  show_if (Union[str, Dict[str, str], None]): Condition to determine if the field should be shown. Defaults to None.
1334
1366
  maxlengths (Dict[str, int], optional): A dictionary of field names and their maximum lengths. Default is None.
1335
1367
  choices (Optional[Union[List[Dict[str, str]], Callable]]): A list of choices of genders to use in the prompts, or a callable that returns such a list. Default set of choices includes male, female, nonbinary, prefer-not-to-say, self-described, and unknown.
1368
+ required (Dict[str, bool], optional): A dictionary of field names and if they should be required. Default is None.
1336
1369
 
1337
1370
  Returns:
1338
1371
  List[Dict[str, str]]: A list of dictionaries with field prompts for gender.
@@ -1376,13 +1409,18 @@ class ALIndividual(Individual):
1376
1409
  if field["field"] in maxlengths:
1377
1410
  field["maxlength"] = maxlengths[field["field"]]
1378
1411
 
1412
+ if required:
1413
+ for field in fields:
1414
+ if field["field"] in required:
1415
+ field["required"] = required[field["field"]]
1416
+
1379
1417
  return fields
1380
1418
 
1381
1419
  def pronoun_fields(
1382
1420
  self,
1383
1421
  show_help=False,
1384
1422
  show_if: Union[str, Dict[str, str], None] = None,
1385
- required: bool = False,
1423
+ required: Union[bool, Dict[str, bool]] = False,
1386
1424
  shuffle: bool = False,
1387
1425
  show_unknown: Optional[Union[Literal["guess"], bool]] = "guess",
1388
1426
  maxlengths: Optional[Dict[str, int]] = None,
@@ -1394,7 +1432,7 @@ class ALIndividual(Individual):
1394
1432
  Args:
1395
1433
  show_help (bool): Whether to show additional help text. Defaults to False.
1396
1434
  show_if (Union[str, Dict[str, str], None]): Condition to determine if the field should be shown. Defaults to None.
1397
- required (bool): Whether the field is required. Defaults to False.
1435
+ required (Union[bool, Dict[str, bool]]): Whether the field is required. Can be a boolean (applies to all fields) or a dictionary of field names and if they should be required. Defaults to False.
1398
1436
  shuffle (bool): Whether to shuffle the order of pronouns. Defaults to False.
1399
1437
  show_unknown (Union[Literal["guess"], bool]): Whether to show an "unknown" option. Can be "guess", True, or False. Defaults to "guess".
1400
1438
  maxlengths (Dict[str, int], optional): A dictionary of field names and their maximum lengths. Default is None.
@@ -1428,7 +1466,7 @@ class ALIndividual(Individual):
1428
1466
  "datatype": "checkboxes",
1429
1467
  "choices": shuffled_choices + final_choices,
1430
1468
  "none of the above": str(self.pronoun_prefer_not_to_say_label),
1431
- "required": required,
1469
+ "required": required if isinstance(required, bool) else False,
1432
1470
  },
1433
1471
  self_described_input,
1434
1472
  ]
@@ -1443,6 +1481,12 @@ class ALIndividual(Individual):
1443
1481
  if field["field"] in maxlengths:
1444
1482
  field["maxlength"] = maxlengths[field["field"]]
1445
1483
 
1484
+ # Handle dictionary-based required parameter
1485
+ if isinstance(required, dict):
1486
+ for field in fields:
1487
+ if field["field"] in required:
1488
+ field["required"] = required[field["field"]]
1489
+
1446
1490
  return fields
1447
1491
 
1448
1492
  def get_pronouns(self) -> set:
@@ -1481,6 +1525,7 @@ class ALIndividual(Individual):
1481
1525
  style: str = "radio",
1482
1526
  show_if: Union[str, Dict[str, str], None] = None,
1483
1527
  maxlengths: Optional[Dict[str, int]] = None,
1528
+ required: Optional[Dict[str, bool]] = None,
1484
1529
  ) -> List[Dict[str, str]]:
1485
1530
  """
1486
1531
  Generate fields for capturing language preferences.
@@ -1490,6 +1535,7 @@ class ALIndividual(Individual):
1490
1535
  style (str): The display style of choices. Defaults to "radio".
1491
1536
  show_if (Union[str, Dict[str, str], None]): Condition to determine if the field should be shown. Defaults to None.
1492
1537
  maxlengths (Dict[str, int], optional): A dictionary of field names and their maximum lengths. Default is None.
1538
+ required (Dict[str, bool], optional): A dictionary of field names and if they should be required. Default is None.
1493
1539
 
1494
1540
  Returns:
1495
1541
  List[Dict[str, str]]: A list of dictionaries with field prompts for language preferences.
@@ -1524,6 +1570,12 @@ class ALIndividual(Individual):
1524
1570
  for field in fields:
1525
1571
  if field["field"] in maxlengths:
1526
1572
  field["maxlength"] = maxlengths[field["field"]]
1573
+
1574
+ if required:
1575
+ for field in fields:
1576
+ if field["field"] in required:
1577
+ field["required"] = required[field["field"]]
1578
+
1527
1579
  return fields
1528
1580
 
1529
1581
  def language_name(self) -> str:
@@ -2360,47 +2412,6 @@ def filter_letters(letter_strings: Union[List[str], str]) -> str:
2360
2412
  # Note: removed "combined_locations" because it is too tightly coupled to MACourts.py right now
2361
2413
 
2362
2414
 
2363
- def fa_icon(
2364
- icon: str, color: str = "primary", color_css: Optional[str] = None, size: str = "sm"
2365
- ) -> str:
2366
- """
2367
- Return HTML for a font-awesome icon of the specified size and color. You can reference
2368
- a CSS variable (such as Bootstrap theme color) or a true CSS color reference, such as 'blue' or
2369
- '#DDDDDD'. Defaults to Bootstrap theme color "primary".
2370
-
2371
- Args:
2372
- icon (str): The name of the icon to use. See https://fontawesome.com/icons for a list of icons.
2373
- color (str): The color of the icon. Defaults to "primary".
2374
- color_css (Optional[str]): A CSS variable or color reference. Defaults to None.
2375
- size (str): The size of the icon. Defaults to "sm".
2376
-
2377
- Returns:
2378
- str: HTML for the icon.
2379
- """
2380
- if not color and not color_css:
2381
- return ":" + icon + ":" # Default to letting Docassemble handle it
2382
- elif color_css:
2383
- return (
2384
- '<i class="fa fa-'
2385
- + icon
2386
- + " fa-"
2387
- + size
2388
- + '" style="color:'
2389
- + color_css
2390
- + ';"></i>'
2391
- )
2392
- else:
2393
- return (
2394
- '<i class="fa fa-'
2395
- + icon
2396
- + " fa-"
2397
- + size
2398
- + '" style="color:var(--'
2399
- + color
2400
- + ');"></i>'
2401
- )
2402
-
2403
-
2404
2415
  def is_sms_enabled() -> bool:
2405
2416
  """Checks if SMS (Twilio) is enabled on the server. Does not verify that it works.
2406
2417
 
@@ -162,7 +162,7 @@ subquestion: |
162
162
  This can take a few minutes.
163
163
 
164
164
  <div class="spinner-border text-primary d-flex justify-content-center" role="status">
165
- <span class="sr-only">Making documents...</span>
165
+ <span class="sr-only visually-hidden">Making documents...</span>
166
166
  </div>
167
167
  event: al_download_waiting_screen
168
168
  reload: True
@@ -192,7 +192,7 @@ subquestion: |
192
192
  This can take a few minutes.
193
193
 
194
194
  <div class="spinner-border text-primary d-flex justify-content-center" role="status">
195
- <span class="sr-only">Generating preview...</span>
195
+ <span class="sr-only visually-hidden">Generating preview...</span>
196
196
  </div>
197
197
  event: al_preview_waiting_screen
198
198
  reload: True
@@ -1,7 +1,27 @@
1
1
  /*
2
- * given a docassemble event name, and text ID containing email address and whether
2
+ * Helper function to process multiple email addresses from a string
3
+ * Splits on comma or semicolon and trims whitespace
4
+ */
5
+ function process_multiple_emails(email_string) {
6
+ if (!email_string || typeof email_string !== 'string') {
7
+ return email_string;
8
+ }
9
+
10
+ // Split on comma or semicolon, trim whitespace, and filter out empty strings
11
+ var emails = email_string.split(/[,;]/).map(function(email) {
12
+ return email.trim();
13
+ }).filter(function(email) {
14
+ return email.length > 0;
15
+ });
16
+
17
+ // Return single string if only one email, array if multiple
18
+ return emails.length === 1 ? emails[0] : emails;
19
+ }
20
+
21
+ /*
22
+ * given a docassemble event name, and text ID containing email address(es) and whether
3
23
  * or not user wants to send editable files, trigger docassemble event to
4
- * email an ALDocumentBundle
24
+ * email an ALDocumentBundle. Supports multiple email addresses separated by commas or semicolons.
5
25
  */
6
26
  function aldocument_send_action(event_name, wants_editable_id, email_id, template_name="", key="final", preferred_formats=null) {
7
27
  var editable = null;
@@ -18,7 +38,8 @@ function aldocument_send_action(event_name, wants_editable_id, email_id, templat
18
38
  }
19
39
  }
20
40
 
21
- var email = $('#' + email_id)[0].value;
41
+ var email_raw = $('#' + email_id)[0].value;
42
+ var email = process_multiple_emails(email_raw);
22
43
  da_action_perform(event_name, {editable: editable, email: email, key:key, template_name: template_name, preferred_formats: final_formats});
23
44
  };
24
45
 
@@ -842,13 +842,13 @@ def interview_list_html(
842
842
  table += f"""
843
843
  </td>
844
844
  <td>
845
- <a href="{ url_action(delete_action, filename=answer.get("filename"), session=answer.get("key")) }"><i class="far fa-trash-alt" title="{ delete_label }" aria-hidden="true"></i><span class="sr-only">{ delete_label }</span></a>
845
+ <a href="{ url_action(delete_action, filename=answer.get("filename"), session=answer.get("key")) }"><i class="far fa-trash-alt" title="{ delete_label }" aria-hidden="true"></i><span class="sr-only visually-hidden">{ delete_label }</span></a>
846
846
  """
847
847
  if show_view_button:
848
848
  table += f"""
849
849
  <a target="_blank" href="{ interview_url(i=answer.get("filename"), session=answer.get("key")) }">
850
850
  <i class="far fa-eye" aria-hidden="true" title="{ view_label }"></i>
851
- <span class="sr-only">{ view_label }</span>
851
+ <span class="sr-only visually-hidden">{ view_label }</span>
852
852
  </a>
853
853
  """
854
854
  table += """
@@ -1133,7 +1133,7 @@ def session_list_html(
1133
1133
  <td class="al-progress-box">{ radial_progress(answer) }
1134
1134
  </td>
1135
1135
  <td>
1136
- <a class="al-sessions-action-rename" href="{ url_ask_rename }"><i class="fa-solid fa-tag" aria-hidden="true" title="{ rename_label }"></i><span class="sr-only">{ rename_label }</span></a>
1136
+ <a class="al-sessions-action-rename" href="{ url_ask_rename }"><i class="fa-solid fa-tag" aria-hidden="true" title="{ rename_label }"></i><span class="sr-only visually-hidden">{ rename_label }</span></a>
1137
1137
  """
1138
1138
  if (
1139
1139
  get_config("assembly line", {}).get("enable answer sets")
@@ -1141,11 +1141,11 @@ def session_list_html(
1141
1141
  ):
1142
1142
  table += f"""
1143
1143
  &nbsp;
1144
- <a class="al-sessions-actions-clone" href="{ url_ask_copy }"><i class="fa-regular fa-clone" aria-hidden="true" title="{clone_label}"></i><span class="sr-only">{ clone_label }</span></a>
1144
+ <a class="al-sessions-actions-clone" href="{ url_ask_copy }"><i class="fa-regular fa-clone" aria-hidden="true" title="{clone_label}"></i><span class="sr-only visually-hidden">{ clone_label }</span></a>
1145
1145
  """
1146
1146
  table += f"""
1147
1147
  &nbsp;
1148
- <a href="{ url_ask_delete }" class="text-danger al-delete-session"><i class="far fa-trash-alt text-danger" title="{ delete_label }" aria-hidden="true"></i><span class="sr-only">{ delete_label }</span></a>
1148
+ <a href="{ url_ask_delete }" class="text-danger al-delete-session"><i class="far fa-trash-alt text-danger" title="{ delete_label }" aria-hidden="true"></i><span class="sr-only visually-hidden">{ delete_label }</span></a>
1149
1149
  </td>
1150
1150
  """
1151
1151
  table += "</tr>"
@@ -462,6 +462,157 @@ class TestALIndividual(unittest.TestCase):
462
462
  self.assertEqual(self.individual.name_short(), "Johnny's Sandwiches")
463
463
  self.assertEqual(str(self.individual), "Johnny's Sandwiches")
464
464
 
465
+ def test_name_fields_required(self):
466
+ """Test the required parameter for name_fields method"""
467
+ self.individual.instanceName = "test_individual"
468
+ self.individual.first_name_label = "First name"
469
+ self.individual.middle_name_label = "Middle name"
470
+ self.individual.last_name_label = "Last name"
471
+ self.individual.suffix_label = "Suffix"
472
+ self.individual.title_label = "Title"
473
+ self.individual.business_name_label = "Business name"
474
+
475
+ # Test without required parameter - middle name should default to False
476
+ fields = self.individual.name_fields(
477
+ person_or_business="person",
478
+ title_choices=["a title"],
479
+ suffix_choices=["Jr.", "Sr."],
480
+ )
481
+ middle_field = None
482
+ for field in fields:
483
+ if "middle" in field["field"]:
484
+ middle_field = field
485
+ break
486
+ self.assertIsNotNone(middle_field)
487
+ self.assertEqual(middle_field["required"], False)
488
+
489
+ # Test with required parameter making middle name required
490
+ fields = self.individual.name_fields(
491
+ person_or_business="person",
492
+ required={"test_individual.name.middle": True},
493
+ title_choices=["a title"],
494
+ suffix_choices=["Jr.", "Sr."],
495
+ )
496
+ middle_field = None
497
+ for field in fields:
498
+ if "middle" in field["field"]:
499
+ middle_field = field
500
+ break
501
+ self.assertIsNotNone(middle_field)
502
+ self.assertEqual(middle_field["required"], True)
503
+
504
+ # Test business case with required parameter
505
+ fields = self.individual.name_fields(
506
+ person_or_business="business",
507
+ required={"test_individual.name.first": True},
508
+ title_choices=["a title"],
509
+ suffix_choices=["Jr.", "Sr."],
510
+ )
511
+ business_field = fields[0]
512
+ self.assertEqual(business_field["required"], True)
513
+
514
+ def test_gender_fields_required(self):
515
+ """Test the required parameter for gender_fields method"""
516
+ self.individual.instanceName = "test_individual"
517
+ self.individual.gender_female_label = "Female"
518
+ self.individual.gender_male_label = "Female"
519
+ self.individual.gender_nonbinary_label = "Non-binary"
520
+ self.individual.gender_prefer_not_to_say_label = "Prefer not to say"
521
+ self.individual.gender_prefer_self_described_label = "Prefer self-described"
522
+ self.individual.gender_unknown_label = "Unknown"
523
+ self.individual.gender_self_described_label = "Self described"
524
+ self.individual.gender_label = "Gender"
525
+
526
+ # Test without required parameter
527
+ fields = self.individual.gender_fields()
528
+ gender_field = fields[0]
529
+ self.assertNotIn(
530
+ "required", gender_field
531
+ ) # Should not have required by default
532
+
533
+ # Test with required parameter
534
+ fields = self.individual.gender_fields(
535
+ required={"test_individual.gender": True}
536
+ )
537
+ gender_field = fields[0]
538
+ self.assertEqual(gender_field["required"], True)
539
+
540
+ # Test key not in required dict - should not change default
541
+ fields = self.individual.gender_fields(required={"other_field": True})
542
+ gender_field = fields[0]
543
+ self.assertNotIn("required", gender_field)
544
+
545
+ def test_pronoun_fields_required(self):
546
+ """Test the required parameter for pronoun_fields method (both bool and dict)"""
547
+ self.individual.instanceName = "test_individual"
548
+ self.individual.pronoun_prefer_self_described_label = "Something else"
549
+ self.individual.pronoun_unknown_label = "Unknown"
550
+ self.individual.pronoun_self_described_label = "Self described pronouns"
551
+ self.individual.pronouns_label = "Self described pronouns"
552
+ self.individual.pronoun_prefer_not_to_say_label = "Prefer not to say"
553
+
554
+ # Test with boolean required parameter (existing behavior)
555
+ fields = self.individual.pronoun_fields(
556
+ required=True, choices=[{"They/them/theirs": "they/them/theirs"}]
557
+ )
558
+ pronoun_field = fields[0]
559
+ self.assertEqual(pronoun_field["required"], True)
560
+
561
+ # Test with boolean required parameter = False
562
+ fields = self.individual.pronoun_fields(
563
+ required=False, choices=[{"They/them/theirs": "they/them/theirs"}]
564
+ )
565
+ pronoun_field = fields[0]
566
+ self.assertEqual(pronoun_field["required"], False)
567
+
568
+ # Test with dictionary required parameter (new behavior)
569
+ fields = self.individual.pronoun_fields(
570
+ required={"test_individual.pronouns": True},
571
+ choices=[{"They/them/theirs": "they/them/theirs"}],
572
+ )
573
+ pronoun_field = fields[0]
574
+ self.assertEqual(pronoun_field["required"], True)
575
+
576
+ # Test with dictionary required parameter for different field
577
+ fields = self.individual.pronoun_fields(
578
+ required={"test_individual.pronouns_self_described": True},
579
+ choices=[{"They/them/theirs": "they/them/theirs"}],
580
+ )
581
+ pronoun_field = fields[0]
582
+ self_described_field = fields[1]
583
+ self.assertEqual(
584
+ pronoun_field["required"],
585
+ False,
586
+ ) # First field should still be False
587
+ self.assertEqual(self_described_field["required"], True)
588
+
589
+ def test_language_fields_required(self):
590
+ """Test the required parameter for language_fields method"""
591
+ self.individual.instanceName = "test_individual"
592
+ self.individual.language_other_label = "Other language"
593
+ self.individual.language_label = "Language"
594
+
595
+ # Test without required parameter
596
+ fields = self.individual.language_fields()
597
+ language_field = fields[0]
598
+ self.assertNotIn(
599
+ "required", language_field
600
+ ) # Should not have required by default
601
+
602
+ # Test with required parameter
603
+ fields = self.individual.language_fields(
604
+ required={"test_individual.language": True}
605
+ )
606
+ language_field = fields[0]
607
+ self.assertEqual(language_field["required"], True)
608
+
609
+ # Test other language field
610
+ fields = self.individual.language_fields(
611
+ required={"test_individual.language_other": False}
612
+ )
613
+ other_field = fields[1]
614
+ self.assertEqual(other_field["required"], False)
615
+
465
616
 
466
617
  class test_get_visible_al_nav_items(unittest.TestCase):
467
618
  def test_case_1(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 4.1.0
3
+ Version: 4.2.0
4
4
  Home-page: https://courtformsonline.org
5
5
  Author: Suffolk Legal Innovation and Technology Lab
6
6
  Author-email: qsteenhuis@suffolk.edu
@@ -44,7 +44,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d
44
44
  return out
45
45
 
46
46
  setup(name='docassemble.AssemblyLine',
47
- version='4.1.0',
47
+ version='4.2.0',
48
48
  description=(''),
49
49
  long_description='# Suffolk LIT Lab Document Assembly Line\r\n\r\n[![PyPI version](https://badge.fury.io/py/docassemble-AssemblyLine.svg)](https://badge.fury.io/py/docassemble-AssemblyLine)\r\n\r\n<img src="https://user-images.githubusercontent.com/7645641/142245862-c2eb02ab-3090-4e97-9653-bb700bf4c54d.png" alt="drawing" width="300" alt="work together" style="align: center;"/>\r\n\r\nThe Assembly Line Project is a collection of volunteers, students, and institutions who joined together\r\nduring the COVID-19 pandemic to help increase access to the court system. Our vision is mobile-friendly,\r\neasy to use **guided** online forms that help empower litigants to access the court remotely.\r\n\r\nOur signature project is [CourtFormsOnline.org](https://courtformsonline.org).\r\n\r\nWe designed a step-by-step, assembly line style process for automating court forms on top of Docassemble\r\nand built several tools along the way that **you** can use in your home jurisdiction.\r\n\r\nThis package contains **runtime code** and **pre-written questions** to support authoring robust, \r\nconsistent, and attractive Docassemble interviews that help complete court forms.\r\n\r\nRead more on our [documentation page](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/).\r\n\r\n\r\n# Related repositories\r\n\r\n* https://github.com/SuffolkLitLab/docassemble-ALWeaver\r\n* https://github.com/SuffolkLitLab/docassemble-ALMassachusetts\r\n* https://github.com/SuffolkLitLab/docassemble-MassAccess\r\n* https://github.com/SuffolkLitLab/docassemble-ALThemeTemplate\r\n* https://github.com/SuffolkLitLab/EfileProxyServer\r\n\r\n# Documentation\r\n\r\nhttps://suffolklitlab.org/docassemble-AssemblyLine-documentation/\r\n\r\n# Installation\r\n\r\n## Menu-driven installation\r\n\r\nThe recommended installation method is with the [guided installation script](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation).\r\n\r\n## Manual installation\r\n\r\nNormally you do not need to manually install the Assembly Line. Use the [installation script](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation)\r\nif you can. The instructions below are for your optional reference.\r\n\r\nThis package depends on the following configuration changes on your Docassemble server:\r\n\r\n* Ensure that [font-awesome](https://docassemble.org/docs/config.html#default%20icons) is enabled (this may be on by default:\r\n```yaml\r\ndefault icons: font awesome\r\n```\r\n* Add a [Google API key](https://docassemble.org/docs/config.html#google) that has access to:\r\n * Google Places API\r\n * Google Geocoding API\r\n* Add a [VoiceRSS API key](https://docassemble.org/docs/config.html#voicerss)\r\n* Add a [Twilio API key](https://docassemble.org/docs/config.html#twilio) for SMS support\r\n* Add an email account: [Mailgun](https://docassemble.org/docs/config.html#mailgun%20api) or [SendGrid](https://docassemble.org/docs/config.html#sendgrid%20api) recommended for email support\r\n* To show package update time and to enable the feedback form, add a GitHub Private Access token to your config.yml file, like this:\r\n```yaml\r\n# Needs access to create new issues on repositories\r\ngithub issues:\r\n username: "suffolklitlab-issues"\r\n token: "12345"\r\n# Does not need any special access to public repositories \r\ngithub readonly:\r\n username: "suffolklitlab-issues"\r\n password: "45678"\r\n type: "basic"\r\n```\r\n* If you are also using the [Assembly Line Weaver](https://github.com/SuffolkLITLab/docassemble-assemblylinewizard), you may want to set up a [Docassemble API key](https://docassemble.org/docs/api.html#manage_api) in your config.yml file to allow you to install packages automatically, like this:\r\n```yaml\r\ninstall packages api key: 12345\r\n```\r\n\r\n# Migration\r\n\r\nSee [discussion here](https://github.com/SuffolkLITLab/docassemble-AssemblyLine/issues/69)\r\n\r\n\r\n# ALDocument class\r\n\r\n## Purpose\r\n\r\nThe ALDocument class is a small utility library that makes it simpler to use the following features in an interview:\r\n\r\n* Conditional assembly of multiple, optional documents that are triggered in different ways in your interview\r\n* An addendum for PDF files that makes it simple to deal with overflow text\r\n* A customizable download screen that lists the documents in a neat table\r\n* A customizable "send" button that allows the user to email the final forms to a location of their choice\r\n\r\nHere is a small snippet that you can copy and modify that shows how to use the most important features of the ALDocument class.\r\n\r\n```\r\n---\r\nobjects:\r\n - CRA_Motion_to_Dismiss_attachment: ALDocument.using(filename="CRA_Motion_to_Dismiss", title="Motion to Dismiss CRA", enabled=True, has_addendum=True, default_overflow_message="[See addendum]") \r\n---\r\nobjects:\r\n - al_user_bundle: ALDocumentBundle.using(elements=[CRA_Motion_to_Dismiss_attachment], title="Forms to download and deliver to court", filename="motion_to_dismiss_CRA.pdf")\r\n - al_court_bundle: ALDocumentBundle.using(elements=[CRA_Motion_to_Dismiss_attachment], title="Forms to download and deliver to court", filename="motion_to_dismiss_CRA.pdf")\r\n---\r\ngeneric object: ALDocument\r\nattachment:\r\n variable name: x.addendum\r\n docx template file: docx_addendum.docx\r\n---\r\ncode: |\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields[\'reasons_for_request\'].overflow_trigger = 640\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields[\'reasons_for_request\'].label = "Reasons for request"\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields.gathered = True\r\n \r\n---\r\nattachment:\r\n variable name: CRA_Motion_to_Dismiss_attachment[i]\r\n name: CRA Motion to Dismiss\r\n filename: CRA_Motion_to_Dismiss\r\n skip undefined: True\r\n pdf template file: CRA_Motion_to_Dismiss.pdf\r\n fields: \r\n - "court_county": ${ trial_court.address.county }\r\n - "docket_number": ${ docket_number }\r\n - "user_signature": ${ users[0].signature_if_final(i) }\r\n - "signature_date": ${ signature_date }\r\n```\r\n\r\nIt is very common to have a *contingent* document in ALDocument. If your document is contingent, remove the `enabled=True` from the object declaration, and use\r\nsome other method to "turn on" the attachment.\r\n\r\nE.g.,\r\n\r\n```\r\ncode: |\r\n CRA_Motion_to_Dismiss_attachment.enabled = condition1 and condition2\r\n```\r\n\r\n# Changelog\r\n\r\nSee [CHANGELOG.MD](https://github.com/SuffolkLITLab/docassemble-AssemblyLine/blob/main/CHANGELOG.md)\r\n',
50
50
  long_description_content_type='text/markdown',
@@ -1 +0,0 @@
1
- __version__ = '4.1.0'