docassemble.AssemblyLine 4.3.0__tar.gz → 4.5.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 (101) hide show
  1. {docassemble_assemblyline-4.3.0/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-4.5.0}/PKG-INFO +2 -2
  2. docassemble_assemblyline-4.5.0/docassemble/AssemblyLine/__init__.py +1 -0
  3. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/al_courts.py +1 -3
  4. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/al_document.py +115 -9
  5. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/al_general.py +10 -9
  6. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/custom_jinja_filters.py +50 -5
  7. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_code.yml +67 -30
  8. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_document.yml +62 -39
  9. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_language.yml +4 -4
  10. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +7 -7
  11. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +51 -19
  12. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +22 -15
  13. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +5 -1
  14. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +20 -14
  15. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +59 -27
  16. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +2 -2
  17. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +11 -5
  18. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/feedback.yml +1 -2
  19. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +59 -45
  20. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +83 -47
  21. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +1 -1
  22. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/ql_namechange.yml +1 -1
  23. docassemble_assemblyline-4.5.0/docassemble/AssemblyLine/data/questions/reserved_words.yml +94 -0
  24. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +5 -5
  25. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +7 -7
  26. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml +1 -1
  27. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_preferred_name.yml +7 -7
  28. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +12 -9
  29. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/ar-words.yml +1 -1
  30. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/es-words.yml +3 -3
  31. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/fr-words.yml +3 -3
  32. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/ne-words.yml +3 -3
  33. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/sessions.py +2 -6
  34. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/test_al_general.py +14 -1
  35. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +2 -2
  36. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble.AssemblyLine.egg-info/requires.txt +1 -1
  37. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/pyproject.toml +2 -2
  38. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/setup.py +2 -2
  39. docassemble_assemblyline-4.3.0/docassemble/AssemblyLine/__init__.py +0 -1
  40. docassemble_assemblyline-4.3.0/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -92
  41. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/LICENSE +0 -0
  42. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/MANIFEST.in +0 -0
  43. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/README.md +0 -0
  44. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  45. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  46. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  47. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  48. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  49. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  50. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  51. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  52. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  53. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_catchall.yml +0 -0
  54. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  55. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_if_final.yml +0 -0
  56. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_preferred_formats.yml +0 -0
  57. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/questions/test_signature.yml +0 -0
  58. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  59. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  60. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  61. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  62. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  63. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  64. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  65. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +0 -0
  66. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  67. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  68. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  69. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  70. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  71. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  72. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  73. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/aldocument.js +0 -0
  74. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  75. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  76. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  77. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  78. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  79. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  80. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  81. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  82. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  83. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  84. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  85. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  86. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  87. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  88. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  89. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/test_catchall.docx +0 -0
  90. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/data/templates/test_if_final.docx +0 -0
  91. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/language.py +0 -0
  92. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/py.typed +0 -0
  93. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/requirements.txt +0 -0
  94. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/sign.py +0 -0
  95. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/test_al_document.py +0 -0
  96. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble/AssemblyLine/test_language.py +0 -0
  97. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +0 -0
  98. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  99. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble.AssemblyLine.egg-info/not-zip-safe +0 -0
  100. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/docassemble.AssemblyLine.egg-info/top_level.txt +0 -0
  101. {docassemble_assemblyline-4.3.0 → docassemble_assemblyline-4.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 4.3.0
3
+ Version: 4.5.0
4
4
  Summary: Quickly go from a paper court form to a runnable, guided, step-by-step web application powered by Docassemble. Swap out branding and pre-built questions to meet your needs.
5
5
  Home-page: https://courtformsonline.org
6
6
  Author: Suffolk Legal Innovation and Technology Lab
@@ -9,7 +9,7 @@ License-Expression: MIT
9
9
  Project-URL: Homepage, https://courtformsonline.org
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: docassemble.ALToolbox>=0.12.0
12
+ Requires-Dist: docassemble.ALToolbox>=0.16.0
13
13
  Requires-Dist: docassemble.GitHubFeedbackForm>=0.4.1.1
14
14
  Dynamic: author
15
15
  Dynamic: home-page
@@ -0,0 +1 @@
1
+ __version__ = '4.5.0'
@@ -2,13 +2,11 @@
2
2
  Package for a very simple / MVP list of courts that is mostly signature compatible w/ MACourts for now
3
3
  """
4
4
 
5
- import os
6
- from typing import Any, Callable, Dict, List, Mapping, Optional, Union, Set, Tuple
5
+ from typing import Any, Callable, Dict, List, Optional, Union, Set, Tuple
7
6
  import pandas as pd
8
7
  import docassemble.base.functions
9
8
  from docassemble.base.util import (
10
9
  path_and_mimetype,
11
- Address,
12
10
  LatitudeLongitude,
13
11
  DAObject,
14
12
  log,
@@ -35,7 +35,6 @@ from docassemble.base.pdfa import pdf_to_pdfa
35
35
  from textwrap import wrap
36
36
  from math import floor
37
37
  import subprocess
38
- from collections import ChainMap
39
38
  import pikepdf
40
39
  from typing import Tuple
41
40
  import secrets
@@ -1582,6 +1581,7 @@ class ALDocumentBundle(DAList):
1582
1581
  self.auto_gather = False
1583
1582
  if "gathered" not in kwargs:
1584
1583
  self.gathered = True
1584
+ self._set_default_attributes()
1585
1585
  self.initializeAttribute("cache", DALazyAttribute)
1586
1586
  self.always_enabled = hasattr(self, "enabled") and self.enabled
1587
1587
  # Pre-cache some DALazyTemplates we set up to aid translation that won't
@@ -1590,6 +1590,24 @@ class ALDocumentBundle(DAList):
1590
1590
  # When the key is "preview", append it to the file name
1591
1591
  self.suffix_to_append = "preview"
1592
1592
 
1593
+ def _set_default_attributes(self) -> None:
1594
+ if not hasattr(self, "add_page_numbers"):
1595
+ self.add_page_numbers = False
1596
+ if not hasattr(self, "page_number_prefix"):
1597
+ self.page_number_prefix = ""
1598
+ if not hasattr(self, "page_number_start"):
1599
+ self.page_number_start = 1
1600
+ if not hasattr(self, "page_number_digits"):
1601
+ self.page_number_digits = 5
1602
+ if not hasattr(self, "page_number_area"):
1603
+ self.page_number_area = None
1604
+ if not hasattr(self, "page_number_font_size"):
1605
+ self.page_number_font_size = 10
1606
+ if not hasattr(self, "page_number_offset_horizontal"):
1607
+ self.page_number_offset_horizontal = 15
1608
+ if not hasattr(self, "page_number_offset_vertical"):
1609
+ self.page_number_offset_vertical = 15
1610
+
1593
1611
  def as_pdf(
1594
1612
  self,
1595
1613
  key: str = "final",
@@ -1649,6 +1667,17 @@ class ALDocumentBundle(DAList):
1649
1667
  filename=f"{base_name(self.filename)}{append_suffix}.pdf",
1650
1668
  pdfa=pdfa,
1651
1669
  )
1670
+ if hasattr(self, "add_page_numbers") and self.add_page_numbers:
1671
+ self._set_default_attributes()
1672
+ pdf.bates_number(
1673
+ prefix=self.page_number_prefix,
1674
+ start=self.page_number_start,
1675
+ digits=self.page_number_digits,
1676
+ area=self.page_number_area,
1677
+ font_size=self.page_number_font_size,
1678
+ offset_horizontal=self.page_number_offset_horizontal,
1679
+ offset_vertical=self.page_number_offset_vertical,
1680
+ )
1652
1681
  pdf.title = self.title
1653
1682
  setattr(self.cache, safe_key, pdf)
1654
1683
 
@@ -2386,6 +2415,7 @@ class ALDocumentBundle(DAList):
2386
2415
  label: str = "Send",
2387
2416
  icon: str = "envelope",
2388
2417
  preferred_formats: Optional[Union[str, List[str]]] = None,
2418
+ email_legend_class: str = "h4",
2389
2419
  ) -> str:
2390
2420
  """
2391
2421
  Generate HTML for an input box and button that allows someone to send the bundle
@@ -2405,6 +2435,8 @@ class ALDocumentBundle(DAList):
2405
2435
  icon (str, optional): The Fontawesome icon for the button. Defaults
2406
2436
  to "envelope".
2407
2437
  preferred_formats (Optional[Union[str,List[str]]], optional): A list of allowed formats for the document. Defaults to "pdf" if not specified.
2438
+ email_legend_class (str, optional): CSS class applied to the email
2439
+ section legend. Defaults to "h4".
2408
2440
 
2409
2441
  Returns:
2410
2442
  str: The generated HTML string for the input box and button.
@@ -2444,7 +2476,7 @@ class ALDocumentBundle(DAList):
2444
2476
  # Container of whole email section with header
2445
2477
  return_str = f"""
2446
2478
  <fieldset class="al_send_bundle al_send_section_alone {html_safe_str(self.instanceName)}" id="al_send_bundle_{name}" name="al_send_bundle_{name}">
2447
- <legend class="h4 al_doc_email_header">{str(self.get_email_copy)}</legend>
2479
+ <legend class="{email_legend_class} al_doc_email_header">{str(self.get_email_copy)}</legend>
2448
2480
  """
2449
2481
  # "Editable" checkbox
2450
2482
  if (
@@ -2736,9 +2768,14 @@ class ALExhibit(DAObject):
2736
2768
  self,
2737
2769
  *,
2738
2770
  refresh: bool = False,
2739
- prefix: str = "",
2740
2771
  pdfa: bool = False,
2741
2772
  add_page_numbers: bool = True,
2773
+ page_number_prefix: str = "",
2774
+ page_number_digits: int = 5,
2775
+ page_number_area=None,
2776
+ page_number_font_size: float = 10,
2777
+ page_number_offset_horizontal: float = 15,
2778
+ page_number_offset_vertical: float = 15,
2742
2779
  add_cover_page: bool = True,
2743
2780
  filename: Optional[str] = None,
2744
2781
  append_matching_suffix: bool = True,
@@ -2750,9 +2787,14 @@ class ALExhibit(DAObject):
2750
2787
 
2751
2788
  Args:
2752
2789
  refresh (bool): If True, forces the exhibit to refresh before generating the PDF. (unused, provided for signature compatibility)
2753
- prefix (str): Prefix for Bates numbering if 'add_page_numbers' is True.
2754
2790
  pdfa (bool): If True, the generated PDF will be in PDF/A format.
2755
2791
  add_page_numbers (bool): If True, apply Bates numbering starting from 'self.start_page'.
2792
+ page_number_prefix (str): If add_page_numbers is True, this gets added to the beginning on the bates number each page (e.g. `EX-`).
2793
+ page_number_digits (int): How many digits (i.e. leading 0s) that the bates number will have
2794
+ page_number_area (str): Where on the page the bates number will go ("TOP_LEFT", "TOP_RIGHT", "BOTTOM_LEFT", "BOTTOM_RIGHT" (default))
2795
+ page_number_font_size (float): How big the bates page number will be in points (default is 10).
2796
+ page_number_offset_horizontal (float): The number of pixels that the bates page number is offset from the left / right of the page.
2797
+ page_number_offset_vertical (float): The number of pixels that the bates page number is offset from the top / bottom of the page.
2756
2798
  add_cover_page (bool): If True, prepend the exhibit with a cover page.
2757
2799
  filename (Optional[str]): Custom filename for the generated PDF. Default is "exhibits.pdf".
2758
2800
  append_matching_suffix (bool): If True, appends a suffix to the filename based on certain matching criteria.
@@ -2780,7 +2822,15 @@ class ALExhibit(DAObject):
2780
2822
  )
2781
2823
 
2782
2824
  if add_page_numbers:
2783
- concatenated_pages.bates_number(prefix=prefix, start=self.start_page)
2825
+ concatenated_pages.bates_number(
2826
+ start=self.start_page,
2827
+ prefix=page_number_prefix,
2828
+ digits=page_number_digits,
2829
+ area=page_number_area,
2830
+ font_size=page_number_font_size,
2831
+ offset_horizontal=page_number_offset_horizontal,
2832
+ offset_vertical=page_number_offset_vertical,
2833
+ )
2784
2834
 
2785
2835
  setattr(self._cache, safe_key, concatenated_pages)
2786
2836
  return getattr(self._cache, safe_key)
@@ -2914,6 +2964,12 @@ class ALExhibitList(DAList):
2914
2964
  filename="file.pdf",
2915
2965
  pdfa: bool = False,
2916
2966
  add_page_numbers: bool = False,
2967
+ page_number_prefix: str = "",
2968
+ page_number_digits: int = 5,
2969
+ page_number_area=None,
2970
+ page_number_font_size: float = 10,
2971
+ page_number_offset_horizontal: float = 15,
2972
+ page_number_offset_vertical: float = 15,
2917
2973
  toc_pages: int = 0,
2918
2974
  append_matching_suffix: bool = True,
2919
2975
  ) -> DAFile:
@@ -2924,6 +2980,12 @@ class ALExhibitList(DAList):
2924
2980
  filename (str): Desired filename for the generated PDF.
2925
2981
  pdfa (bool): If True, generates the PDF in PDF/A format.
2926
2982
  add_page_numbers (bool): If True, adds page numbers to the generated PDF.
2983
+ page_number_prefix (str): What the bates number added to each page should start with (e.g. `EX-`).
2984
+ page_number_digits (int): How many digits (i.e. leading 0s) that the bates number will have
2985
+ page_number_area (str): Where on the page the bates number will go ("TOP_LEFT", "TOP_RIGHT", "BOTTOM_LEFT", "BOTTOM_RIGHT" (default))
2986
+ page_number_font_size (float): How big the bates page number will be in points (default is 10).
2987
+ page_number_offset_horizontal (float): The number of pixels that the bates page number is offset from the left / right of the page.
2988
+ page_number_offset_vertical (float): The number of pixels that the bates page number is offset from the top / bottom of the page.
2927
2989
  toc_pages (int): Expected number of pages in the table of contents.
2928
2990
  append_matching_suffix (bool): If True, appends matching suffix to the filename.
2929
2991
 
@@ -2935,12 +2997,19 @@ class ALExhibitList(DAList):
2935
2997
  exhibit.cover_page
2936
2998
  if self.include_table_of_contents and toc_pages != 1:
2937
2999
  self._update_page_numbers(toc_guess_pages=toc_pages)
3000
+ if not page_number_prefix and self.bates_prefix:
3001
+ page_number_prefix = self.bates_prefix
2938
3002
  return pdf_concatenate(
2939
3003
  [
2940
3004
  exhibit.as_pdf(
2941
3005
  add_cover_page=self.include_exhibit_cover_pages,
2942
3006
  add_page_numbers=add_page_numbers,
2943
- prefix=self.bates_prefix,
3007
+ page_number_prefix=page_number_prefix,
3008
+ page_number_digits=page_number_digits,
3009
+ page_number_area=page_number_area,
3010
+ page_number_font_size=page_number_font_size,
3011
+ page_number_offset_horizontal=page_number_offset_horizontal,
3012
+ page_number_offset_vertical=page_number_offset_vertical,
2944
3013
  )
2945
3014
  for exhibit in self
2946
3015
  ],
@@ -3073,12 +3142,19 @@ class ALExhibitDocument(ALDocument):
3073
3142
  has_addendum: bool
3074
3143
  auto_labeler: Callable
3075
3144
  auto_ocr: bool
3145
+ # Deprecated
3076
3146
  bates_prefix: str
3077
3147
  maximum_size: int
3078
3148
  maximum_size_per_doc: int
3079
3149
  suffix_to_append: str
3080
3150
  exhibits: ALExhibitList
3081
3151
  table_of_contents: DAFile
3152
+ page_number_prefix: str
3153
+ page_number_digits: int
3154
+ page_number_area: str | None
3155
+ page_number_font_size: float
3156
+ page_number_offset_horizontal: float
3157
+ page_number_offset_vertical: float
3082
3158
 
3083
3159
  def init(self, *pargs, **kwargs) -> None:
3084
3160
  """Standard DAObject init method.
@@ -3093,8 +3169,6 @@ class ALExhibitDocument(ALDocument):
3093
3169
  self.exhibits.auto_labeler = self.auto_labeler
3094
3170
  if hasattr(self, "auto_ocr"):
3095
3171
  self.exhibits.auto_ocr = self.auto_ocr
3096
- if hasattr(self, "bates_prefix"):
3097
- self.exhibits.bates_prefix = self.bates_prefix
3098
3172
  if hasattr(self, "include_exhibit_cover_pages"):
3099
3173
  self.exhibits.include_exhibit_cover_pages = self.include_exhibit_cover_pages
3100
3174
  else:
@@ -3112,13 +3186,30 @@ class ALExhibitDocument(ALDocument):
3112
3186
  else:
3113
3187
  self.include_table_of_contents = True
3114
3188
  self.exhibits.include_table_of_contents = True
3189
+ if hasattr(self, "bates_prefix"):
3190
+ self.exhibits.bates_prefix = self.bates_prefix
3115
3191
  if not hasattr(self, "add_page_numbers"):
3116
3192
  self.add_page_numbers = False
3193
+ self._set_default_attributes()
3117
3194
  self.has_addendum = False
3118
3195
  if not hasattr(self, "suffix_to_append"):
3119
3196
  # When the key is "preview", append it to the file name
3120
3197
  self.suffix_to_append = "preview"
3121
3198
 
3199
+ def _set_default_attributes(self) -> None:
3200
+ if not hasattr(self, "page_number_prefix"):
3201
+ self.page_number_prefix = ""
3202
+ if not hasattr(self, "page_number_digits"):
3203
+ self.page_number_digits = 5
3204
+ if not hasattr(self, "page_number_area"):
3205
+ self.page_number_area = None
3206
+ if not hasattr(self, "page_number_font_size"):
3207
+ self.page_number_font_size = 10
3208
+ if not hasattr(self, "page_number_offset_horizontal"):
3209
+ self.page_number_offset_horizontal = 15
3210
+ if not hasattr(self, "page_number_offset_vertical"):
3211
+ self.page_number_offset_vertical = 15
3212
+
3122
3213
  def has_overflow(self) -> bool:
3123
3214
  """
3124
3215
  Check if there is any overflow in the document.
@@ -3193,12 +3284,21 @@ class ALExhibitDocument(ALDocument):
3193
3284
  filename = base_name(self.filename) + ".pdf"
3194
3285
 
3195
3286
  if len(self.exhibits):
3287
+ self._set_default_attributes()
3196
3288
  if self.include_table_of_contents:
3197
3289
  toc_pages = self.table_of_contents.num_pages()
3198
3290
  return pdf_concatenate(
3199
3291
  self.table_of_contents,
3200
3292
  self.exhibits.as_pdf(
3201
- add_page_numbers=self.add_page_numbers, toc_pages=toc_pages
3293
+ add_page_numbers=self.add_page_numbers,
3294
+ page_number_prefix=self.page_number_prefix,
3295
+ page_number_digits=self.page_number_digits,
3296
+ page_number_area=self.page_number_area,
3297
+ page_number_font_size=self.page_number_font_size,
3298
+ page_number_offset_horizontal=self.page_number_offset_horizontal,
3299
+ page_number_offset_vertical=self.page_number_offset_vertical,
3300
+ toc_pages=toc_pages,
3301
+ pdfa=pdfa,
3202
3302
  ),
3203
3303
  filename=filename,
3204
3304
  pdfa=pdfa,
@@ -3206,6 +3306,12 @@ class ALExhibitDocument(ALDocument):
3206
3306
  else:
3207
3307
  return self.exhibits.as_pdf(
3208
3308
  add_page_numbers=self.add_page_numbers,
3309
+ page_number_prefix=self.page_number_prefix,
3310
+ page_number_digits=self.page_number_digits,
3311
+ page_number_area=self.page_number_area,
3312
+ page_number_font_size=self.page_number_font_size,
3313
+ page_number_offset_horizontal=self.page_number_offset_horizontal,
3314
+ page_number_offset_vertical=self.page_number_offset_vertical,
3209
3315
  filename=filename,
3210
3316
  pdfa=pdfa,
3211
3317
  )
@@ -30,13 +30,11 @@ from docassemble.base.util import (
30
30
  their,
31
31
  url_action,
32
32
  validation_error,
33
- value,
34
33
  word,
35
34
  your,
36
35
  log,
37
36
  value,
38
37
  )
39
- from docassemble.ALToolbox.misc import fa_icon
40
38
  import docassemble.base.functions
41
39
  import random
42
40
  import re
@@ -325,7 +323,7 @@ class ALAddress(Address):
325
323
  if not bare and (
326
324
  unit_lower.isnumeric()
327
325
  or (
328
- not " " in self.unit
326
+ " " not in self.unit
329
327
  and not any(
330
328
  x in unit_lower
331
329
  for x in [
@@ -1660,10 +1658,13 @@ class ALIndividual(Individual):
1660
1658
  Used to assist with checkbox filling in PDFs with "skip undefined"
1661
1659
  turned on.
1662
1660
  """
1663
- return not (
1664
- self.gender
1665
- in ["prefer-not-to-say", "male", "female", "unknown", "nonbinary"]
1666
- )
1661
+ return self.gender not in [
1662
+ "prefer-not-to-say",
1663
+ "male",
1664
+ "female",
1665
+ "unknown",
1666
+ "nonbinary",
1667
+ ]
1667
1668
 
1668
1669
  def contact_fields(self) -> None:
1669
1670
  """
@@ -1913,7 +1914,7 @@ class ALIndividual(Individual):
1913
1914
  if len(pronouns_to_use) > 0:
1914
1915
  output = "/".join(pronouns_to_use)
1915
1916
  else:
1916
- output = default
1917
+ output = f"{default}'s {target}"
1917
1918
  elif hasattr(self, "person_type") and self.person_type in [
1918
1919
  "business",
1919
1920
  "organization",
@@ -1927,7 +1928,7 @@ class ALIndividual(Individual):
1927
1928
  else:
1928
1929
  output = their(target, **kwargs)
1929
1930
  else:
1930
- output = default
1931
+ output = f"{default}'s {target}"
1931
1932
 
1932
1933
  if "capitalize" in kwargs and kwargs["capitalize"]:
1933
1934
  return capitalize(output)
@@ -5,9 +5,8 @@
5
5
 
6
6
  # See: https://docassemble.org/docs/documents.html#register_jinja_filter
7
7
 
8
- import re
9
8
  from typing import Any, Dict, List, Optional, Union
10
- from docassemble.base.util import register_jinja_filter, DACatchAll, word
9
+ from docassemble.base.util import register_jinja_filter, DACatchAll
11
10
  from jinja2 import Undefined, pass_context
12
11
  from jinja2.runtime import Context as Jinja2Context
13
12
 
@@ -18,6 +17,7 @@ __all__ = [
18
17
  "catchall_question",
19
18
  "catchall_subquestion",
20
19
  "catchall_fields_code",
20
+ "catchall_complete",
21
21
  ]
22
22
 
23
23
 
@@ -29,6 +29,7 @@ def catchall_options(value: Any, *raw_items: Any) -> DACatchAll:
29
29
 
30
30
  The items can be in various formats:
31
31
 
32
+ - String: `"code_is_the_label"`
32
33
  - String: `"code: label"`
33
34
  - Dictionary: `{"code": "label"}`
34
35
  - Tuple: `("code", "label")`
@@ -66,14 +67,14 @@ def catchall_options(value: Any, *raw_items: Any) -> DACatchAll:
66
67
  DACatchAll: The modified DACatchAll object with the assigned options.
67
68
  """
68
69
  if isinstance(value, DACatchAll):
69
- pairs = []
70
+ pairs: list[str | tuple[str, str]] = []
70
71
  for item in raw_items:
71
72
  if isinstance(item, str):
72
73
  if ":" in item:
73
74
  code, label = item.split(":", 1)
75
+ pairs.append((code.strip(), label.strip()))
74
76
  else:
75
- code = label = item
76
- pairs.append((code.strip(), label.strip()))
77
+ pairs.append(item.strip())
77
78
  elif isinstance(item, dict):
78
79
  for code, label in item.items():
79
80
  pairs.append((code.strip(), label.strip()))
@@ -203,6 +204,49 @@ def catchall_subquestion(value: Any, subquestion: str) -> DACatchAll:
203
204
  return value
204
205
 
205
206
 
207
+ def catchall_complete(
208
+ value: DACatchAll,
209
+ question: str = "",
210
+ subquestion: str = "",
211
+ label: str = "",
212
+ datatype: str = "",
213
+ options: Any = "",
214
+ ) -> DACatchAll:
215
+ """
216
+ Jinja2 filter to allow you to define multiple attributes of a DACatchAll field inside a DOCX template.
217
+
218
+ Each argument after `value` corresponds to the similarly named `catchall_` function.
219
+
220
+ Example usage in a DOCX template:
221
+ ```
222
+ {{ my_catchall_field | catchall_complete(question="What additional information do you need?", subquestion="Be specific", label="no label") }}
223
+ ```
224
+
225
+ Args:
226
+ value (DACatchAll): The DACatchAll object to which the attributes will be assigned.
227
+ question (str, optional): The question text to set on the DACatchAll object.
228
+ subquestion (str, optional): The subquestion text to set on the DACatchAll object.
229
+ label (str, optional): The label to set on the DACatchAll object.
230
+ datatype (str, optional): The datatype to set on the DACatchAll object.
231
+ options (Any, optional): The options to set on the DACatchAll object.
232
+
233
+ Returns:
234
+ DACatchAll: The modified DACatchAll object with the assigned attributes.
235
+ """
236
+ if isinstance(value, DACatchAll):
237
+ if question:
238
+ catchall_question(value, question)
239
+ if subquestion:
240
+ catchall_subquestion(value, subquestion)
241
+ if label:
242
+ catchall_label(value, label)
243
+ if datatype:
244
+ catchall_datatype(value, datatype)
245
+ if options:
246
+ catchall_options(value, options)
247
+ return value
248
+
249
+
206
250
  def _undefined_label(ud: Undefined) -> Union[str, None]:
207
251
  """
208
252
  Extract the variable name from a Jinja2 Undefined object for use as a placeholder label.
@@ -314,6 +358,7 @@ register_jinja_filter("catchall_label", catchall_label)
314
358
  register_jinja_filter("catchall_datatype", catchall_datatype)
315
359
  register_jinja_filter("catchall_question", catchall_question)
316
360
  register_jinja_filter("catchall_subquestion", catchall_subquestion)
361
+ register_jinja_filter("catchall_complete", catchall_complete)
317
362
  register_jinja_filter("if_final", if_final)
318
363
 
319
364
 
@@ -22,14 +22,14 @@ code: |
22
22
  form_delivery_complete = True
23
23
  ---
24
24
  code: |
25
- court_email = court_emails.get(trial_court.name,None)
25
+ court_email = court_emails.get(trial_court.name, None)
26
26
  if not court_email:
27
27
  found_email = False
28
28
  else:
29
29
  found_email = True
30
30
  ---
31
31
  code: |
32
- bcc_failsafe = court_emails.get('Failsafe Address',None)
32
+ bcc_failsafe = court_emails.get("Failsafe Address", None)
33
33
  ---
34
34
  comment: |
35
35
  You are encouraged to overwrite these emails during testing!
@@ -51,22 +51,50 @@ code: |
51
51
  else:
52
52
  email_to_use = dev_email_to_use
53
53
  bcc_email = dev_bcc_email
54
-
54
+
55
55
  # temporary for testing
56
56
  # email_to_use = "massaccess@suffolk.edu"
57
57
 
58
- if task_not_yet_performed('send email'):
58
+ if task_not_yet_performed("send email"):
59
59
  log(f"Court email is {court_email}, sending email to {email_to_use}")
60
60
  email_success = False
61
61
  if should_cc_user:
62
- log('Sending email to ' + email_to_use + ' and ccing ' + cc_email + ' for ' + str(users) + ' bcc to ' + bcc_email)
63
- email_success = send_email(to=email_to_use, template=email_to_court_template, task='send email',
64
- cc=cc_email, bcc=bcc_email, attachments=al_final_form_to_file.as_pdf_list(key='final'))
62
+ log(
63
+ "Sending email to "
64
+ + email_to_use
65
+ + " and ccing "
66
+ + cc_email
67
+ + " for "
68
+ + str(users)
69
+ + " bcc to "
70
+ + bcc_email
71
+ )
72
+ email_success = send_email(
73
+ to=email_to_use,
74
+ template=email_to_court_template,
75
+ task="send email",
76
+ cc=cc_email,
77
+ bcc=bcc_email,
78
+ attachments=al_final_form_to_file.as_pdf_list(key="final"),
79
+ )
65
80
  else:
66
- log('Sending email to ' + email_to_use + ' and no cc ' + ' for ' + str(users) + ' bcc to ' + bcc_email)
67
- email_success = send_email(to=email_to_use, template=email_to_court_template, task='send email',
68
- attachments=al_final_form_to_file.as_pdf_list(key='final'), bcc=bcc_email)
69
- mark_task_as_performed('send email')
81
+ log(
82
+ "Sending email to "
83
+ + email_to_use
84
+ + " and no cc "
85
+ + " for "
86
+ + str(users)
87
+ + " bcc to "
88
+ + bcc_email
89
+ )
90
+ email_success = send_email(
91
+ to=email_to_use,
92
+ template=email_to_court_template,
93
+ task="send email",
94
+ attachments=al_final_form_to_file.as_pdf_list(key="final"),
95
+ bcc=bcc_email,
96
+ )
97
+ mark_task_as_performed("send email")
70
98
  sent_email_to_court = True
71
99
  ---
72
100
  ##############################################################
@@ -90,7 +118,7 @@ code: |
90
118
  # Gets the number of pages, plus the first cover page
91
119
  # TODO(brycew): figure out a better way to estimate the number of pages,
92
120
  # including addendum, without generating a full file
93
- al_num_package_pages = al_court_bundle.as_pdf(key='preview').num_pages() + 1
121
+ al_num_package_pages = al_court_bundle.as_pdf(key="preview").num_pages() + 1
94
122
  ---
95
123
  code: |
96
124
  # You can define this in the interview's code block.
@@ -101,7 +129,7 @@ code: |
101
129
  # This is a fallback package title to avoid errors on new forms
102
130
  # It defines package_title as the metadata title.
103
131
  # You may want a better form title for most interviews
104
- package_title = all_variables(special='metadata').get('title','').rstrip()
132
+ package_title = all_variables(special="metadata").get("title", "").rstrip()
105
133
  ---
106
134
  attachment:
107
135
  variable name: al_cover_page[i]
@@ -126,27 +154,36 @@ code: |
126
154
  elif signature_choice == "typed_signature":
127
155
  for signature in signature_fields:
128
156
  # Signature fields are strings like "users[0].signature". If the string ends in .signature, remove that and replace with .typed_signature, only at the end of the string
129
- if signature.endswith('signature'):
130
- typed_signature = signature[:-9] + 'typed_signature'
157
+ if signature.endswith("signature"):
158
+ typed_signature = signature[:-9] + "typed_signature"
131
159
  define(signature, DAFile(signature, filename="signature.png"))
132
160
  value(signature).initialize()
133
- create_signature(value(typed_signature), value(signature).path(), signature_prefix = al_typed_signature_prefix, font_name = al_typed_signature_font)
161
+ create_signature(
162
+ value(typed_signature),
163
+ value(signature).path(),
164
+ signature_prefix=al_typed_signature_prefix,
165
+ font_name=al_typed_signature_font,
166
+ )
134
167
  else:
135
- log("Signature field " + signature + " does not end with .signature, not creating typed signature")
136
- elif signature_choice in ['this device', 'this_device']:
168
+ log(
169
+ "Signature field "
170
+ + signature
171
+ + " does not end with .signature, not creating typed signature"
172
+ )
173
+ elif signature_choice in ["this device", "this_device"]:
137
174
  for signature in signature_fields:
138
- value(signature)
175
+ value(signature)
139
176
  # Check for Branch 2 or 3
140
- elif signature_choice == 'phone':
177
+ elif signature_choice == "phone":
141
178
  saw_signature_qrcode
142
179
  # User will click next. link_cell will only be defined if they chose the texting option
143
- if showifdef("link_cell") and task_not_yet_performed('send signature link'):
180
+ if showifdef("link_cell") and task_not_yet_performed("send signature link"):
144
181
  # They used the text option
145
- send_sms(task='send signature link', to=link_cell,template=interview_link)
182
+ send_sms(task="send signature link", to=link_cell, template=interview_link)
146
183
  signature_wait_screen
147
184
  for signature in signature_fields:
148
185
  value(signature)
149
- else: # Branch 3: They used the QR Code. No special screen, just continue
186
+ else: # Branch 3: They used the QR Code. No special screen, just continue
150
187
  for signature in signature_fields:
151
188
  value(signature)
152
189
  # Show the follow-up if they sent the link to their phone by QR or by SMS
@@ -157,14 +194,14 @@ code: |
157
194
  code: |
158
195
  # Sometimes we have values that can be set from interview_metadata.
159
196
  # They don't always work, but interview author can override.
160
-
197
+
161
198
  # Some interviews have multiple interview_metadata keys. Look in the 1st one
162
- first_form_key = next(iter(interview_metadata),'')
163
- if isinstance(interview_metadata.get(first_form_key,{}), dict):
164
- first_form = interview_metadata.get(first_form_key,{})
199
+ first_form_key = next(iter(interview_metadata), "")
200
+ if isinstance(interview_metadata.get(first_form_key, {}), dict):
201
+ first_form = interview_metadata.get(first_form_key, {})
165
202
  else:
166
203
  first_form = {}
167
- del(first_form_key)
204
+ del first_form_key
168
205
  ---
169
206
  depends on:
170
207
  - user_ask_role
@@ -175,12 +212,12 @@ code: |
175
212
  # define user_role to be either "defendant" or "plaintiff",
176
213
  # either by checking interview_metadata or asking the user.
177
214
  # The only valid values for user_role are "plaintiff" and "defendant".
178
-
215
+
179
216
  # We will check for the "typical_role" key.
180
217
  # Some older wizarded code doesn't define `typical_role`, so fall back
181
218
  # to "unknown" if the key is not present.
182
219
  # first_form is the first entry in interview_metadata
183
- if not first_form.get("typical_role", 'unknown') == 'unknown':
220
+ if not first_form.get("typical_role", "unknown") == "unknown":
184
221
  user_role = first_form.get("typical_role")
185
222
  else:
186
223
  # If the user role was set to "unknown" at the time wizard run