docassemble.AssemblyLine 3.5.1__tar.gz → 4.1.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 (100) hide show
  1. {docassemble_assemblyline-3.5.1/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-4.1.0}/PKG-INFO +2 -2
  2. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/__init__.py +1 -0
  3. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/al_document.py +138 -42
  4. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/al_general.py +97 -50
  5. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/custom_jinja_filters.py +341 -0
  6. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_document.yml +29 -3
  7. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +4 -7
  8. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +4 -6
  9. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +126 -26
  10. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +26 -5
  11. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/questions/test_catchall.yml +45 -0
  12. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/questions/test_if_final.yml +27 -0
  13. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/questions/test_preferred_formats.yml +75 -0
  14. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +25 -2
  15. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +1 -1
  16. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  17. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/aldocument.js +11 -5
  18. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/templates/test_catchall.docx +0 -0
  19. docassemble_assemblyline-4.1.0/docassemble/AssemblyLine/data/templates/test_if_final.docx +0 -0
  20. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/sessions.py +141 -43
  21. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/test_al_general.py +4 -3
  22. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/test_language.py +12 -13
  23. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +2 -2
  24. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +6 -2
  25. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble.AssemblyLine.egg-info/requires.txt +1 -1
  26. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/setup.py +2 -2
  27. docassemble_assemblyline-3.5.1/docassemble/AssemblyLine/__init__.py +0 -1
  28. docassemble_assemblyline-3.5.1/docassemble/AssemblyLine/custom_jinja_filters.py +0 -123
  29. docassemble_assemblyline-3.5.1/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  30. docassemble_assemblyline-3.5.1/docassemble/AssemblyLine/project_maintenance.py +0 -432
  31. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/LICENSE +0 -0
  32. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/MANIFEST.in +0 -0
  33. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/README.md +0 -0
  34. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/al_courts.py +0 -0
  35. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_code.yml +0 -0
  36. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_language.yml +0 -0
  37. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  38. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  39. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +0 -0
  40. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +0 -0
  41. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +0 -0
  42. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +0 -0
  43. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  44. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +0 -0
  45. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  46. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +0 -0
  47. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/feedback.yml +0 -0
  48. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +0 -0
  49. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/ql_namechange.yml +0 -0
  50. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -0
  51. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  52. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  53. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +0 -0
  54. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +0 -0
  55. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml +0 -0
  56. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  57. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  58. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  59. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  60. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/questions/test_signature.yml +0 -0
  61. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  62. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/es-words.yml +0 -0
  63. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  64. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  65. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  66. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  67. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  68. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  69. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  70. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  71. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  72. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  73. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  74. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  75. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  76. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  77. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  78. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  79. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  80. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  81. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  82. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  83. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  84. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  85. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  86. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  87. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  88. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  89. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  90. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/language.py +0 -0
  91. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/py.typed +0 -0
  92. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/requirements.txt +0 -0
  93. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/sign.py +0 -0
  94. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/AssemblyLine/test_al_document.py +0 -0
  95. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble/__init__.py +0 -0
  96. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  97. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble.AssemblyLine.egg-info/not-zip-safe +0 -0
  98. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/docassemble.AssemblyLine.egg-info/top_level.txt +0 -0
  99. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/pyproject.toml +0 -0
  100. {docassemble_assemblyline-3.5.1 → docassemble_assemblyline-4.1.0}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 3.5.1
3
+ Version: 4.1.0
4
4
  Home-page: https://courtformsonline.org
5
5
  Author: Suffolk Legal Innovation and Technology Lab
6
6
  Author-email: qsteenhuis@suffolk.edu
7
7
  License: The MIT License (MIT)
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: docassemble.ALToolbox>=0.11.1
10
+ Requires-Dist: docassemble.ALToolbox>=0.12.0
11
11
  Requires-Dist: docassemble.GithubFeedbackForm>=0.4.1.1
12
12
  Dynamic: author
13
13
  Dynamic: author-email
@@ -0,0 +1 @@
1
+ __version__ = '4.1.0'
@@ -36,6 +36,9 @@ import subprocess
36
36
  from collections import ChainMap
37
37
  import pikepdf
38
38
  from typing import Tuple
39
+ import secrets
40
+ import string
41
+
39
42
 
40
43
  __all__ = [
41
44
  "ALAddendumField",
@@ -59,6 +62,22 @@ __all__ = [
59
62
  DEBUG_MODE = get_config("debug")
60
63
 
61
64
 
65
+ def random_suffix(length: int = 8) -> str:
66
+ """Return a random string for use in unique IDs.
67
+
68
+ Note: this is powerful enough for the expected usecase of distinguishing a few
69
+ HTML elements from each other, but not cryptographically secure or as strong as
70
+ a true GUID.
71
+
72
+ Args:
73
+ length (int): The length of the random string to generate. Defaults to 8.
74
+ Returns:
75
+ str: A random string of lowercase letters and digits.
76
+ """
77
+ alphabet = string.ascii_lowercase + string.digits
78
+ return "".join(secrets.choice(alphabet) for _ in range(length))
79
+
80
+
62
81
  def base_name(filename: str) -> str:
63
82
  """
64
83
  Extracts the base name of a file without its extension.
@@ -1653,8 +1672,8 @@ class ALDocumentBundle(DAList):
1653
1672
  refresh: bool = True,
1654
1673
  pdfa: bool = False,
1655
1674
  title: str = "",
1656
- format="pdf",
1657
- include_pdf=True,
1675
+ format: Optional[str] = "pdf",
1676
+ include_pdf: Optional[bool] = True,
1658
1677
  ) -> DAFile:
1659
1678
  """
1660
1679
  Returns a zip file containing all enabled documents in the bundle in the specified format.
@@ -1670,6 +1689,11 @@ class ALDocumentBundle(DAList):
1670
1689
  Returns:
1671
1690
  DAFile: A zip file containing the enabled documents.
1672
1691
  """
1692
+ if format is None:
1693
+ format = "pdf"
1694
+
1695
+ if include_pdf is None:
1696
+ include_pdf = True
1673
1697
 
1674
1698
  zip_key = f"{ space_to_underscore( key )}_zip"
1675
1699
 
@@ -1679,6 +1703,7 @@ class ALDocumentBundle(DAList):
1679
1703
 
1680
1704
  # strip out a possible '.pdf' ending then add '.zip'
1681
1705
  zipname = os.path.splitext(self.filename)[0]
1706
+
1682
1707
  if format == "docx":
1683
1708
  docs = []
1684
1709
  for doc in self.enabled_documents(refresh=refresh):
@@ -1864,6 +1889,8 @@ class ALDocumentBundle(DAList):
1864
1889
  include_zip: bool = True,
1865
1890
  include_full_pdf: bool = False,
1866
1891
  append_matching_suffix: bool = True,
1892
+ zip_include_pdf: Optional[bool] = None,
1893
+ zip_format: Optional[str] = None,
1867
1894
  ) -> Tuple[List[Dict[str, DAFile]], Optional[DAFile], Optional[DAFile]]:
1868
1895
  """
1869
1896
  Generates a cache of all enabled documents in the bundle, and returns it in a structure that can be cached
@@ -1887,6 +1914,8 @@ class ALDocumentBundle(DAList):
1887
1914
  include_zip (bool): Flag to include a zip option, default is True.
1888
1915
  include_full_pdf (bool): Flag to include a PDF version of the whole bundle, default is False.
1889
1916
  append_matching_suffix (bool): Flag to determine if matching suffix should be appended to file name, default is True.
1917
+ zip_include_pdf (Optional[bool]): If True, includes a PDF version in the zip file even if original is in DOCX format.
1918
+ zip_format (Optional[str]): Format of the primary version of each document.
1890
1919
 
1891
1920
  Returns:
1892
1921
  Tuple[List[Dict[str, DAFile]], Optional[DAFile], Optional[DAFile]]: A list of dictionaries containing the enabled documents, a zip file of the whole bundle, and a PDF of the whole
@@ -1940,9 +1969,14 @@ class ALDocumentBundle(DAList):
1940
1969
  pass
1941
1970
  results.append(result)
1942
1971
 
1972
+ if zip_format is None:
1973
+ zip_format = "original" if original else "docx" if docx else "pdf"
1974
+
1943
1975
  if len(enabled_docs) > 1 and include_zip:
1944
1976
  bundled_zip = self.as_zip(
1945
- key=key, format="original" if original else "docx" if docx else "pdf"
1977
+ key=key,
1978
+ format=zip_format,
1979
+ include_pdf=zip_include_pdf,
1946
1980
  )
1947
1981
  else:
1948
1982
  bundled_zip = None
@@ -1976,6 +2010,8 @@ class ALDocumentBundle(DAList):
1976
2010
  use_previously_cached_files: bool = False,
1977
2011
  include_full_pdf: bool = False,
1978
2012
  full_pdf_label: Optional[str] = None,
2013
+ zip_include_pdf: Optional[bool] = True,
2014
+ zip_format: Optional[str] = None,
1979
2015
  ) -> str:
1980
2016
  """
1981
2017
  Constructs an HTML table displaying a list of documents with 'view' and 'download' buttons.
@@ -2002,6 +2038,8 @@ class ALDocumentBundle(DAList):
2002
2038
  use_previously_cached_files (bool): Flag to use previously cached files (e.g., made in background) if defined. default is False.
2003
2039
  include_full_pdf (bool): Flag to include a full PDF option, default is False.
2004
2040
  full_pdf_label (Optional[str]): Label for the full PDF option. If not provided, uses the generic template for `self.full_pdf_label` ("Download all").
2041
+ zip_include_pdf (Optional[bool]): Flag to include PDF files in the zip archive, default is True. If `None` value of `view` will be used.
2042
+ zip_format (Optional[str]): Format of the files in the zip archive. If None, defaults to value of "format" parameter.
2005
2043
 
2006
2044
  Returns:
2007
2045
  str: HTML representation of a table with documents and their associated actions.
@@ -2012,6 +2050,12 @@ class ALDocumentBundle(DAList):
2012
2050
  if not hasattr(self, "_cached_full_pdf_label"):
2013
2051
  self._cached_full_pdf_label = str(self.full_pdf_label)
2014
2052
 
2053
+ if zip_format is None:
2054
+ zip_format = format
2055
+
2056
+ if zip_include_pdf is None:
2057
+ zip_include_pdf = view
2058
+
2015
2059
  if use_previously_cached_files and hasattr(self, "_downloadable_files"):
2016
2060
  downloadable_files, bundled_zip, bundled_pdf = self._downloadable_files
2017
2061
  else:
@@ -2025,6 +2069,8 @@ class ALDocumentBundle(DAList):
2025
2069
  include_zip=include_zip,
2026
2070
  include_full_pdf=include_full_pdf,
2027
2071
  append_matching_suffix=append_matching_suffix,
2072
+ zip_include_pdf=zip_include_pdf,
2073
+ zip_format=zip_format,
2028
2074
  )
2029
2075
 
2030
2076
  html = f'<div class="container al_table al_doc_table" id="{ html_safe_str(self.instanceName) }">'
@@ -2215,7 +2261,7 @@ class ALDocumentBundle(DAList):
2215
2261
  self._cached_include_editable_documents = str(
2216
2262
  self.include_editable_documents
2217
2263
  )
2218
- name = html_safe_str(self.instanceName)
2264
+ name = html_safe_str(self.instanceName) + random_suffix()
2219
2265
  al_wants_editable_input_id = "_ignore_al_wants_editable_" + name
2220
2266
  al_email_input_id = "_ignore_al_doc_email_" + name
2221
2267
  al_send_button_id = "al_send_email_button_" + name
@@ -2250,12 +2296,13 @@ class ALDocumentBundle(DAList):
2250
2296
  def send_button_to_html(
2251
2297
  self,
2252
2298
  email: str,
2253
- editable: bool = False,
2299
+ editable: Optional[bool] = None,
2254
2300
  template_name: str = "",
2255
2301
  label: str = "Send",
2256
2302
  icon: str = "envelope",
2257
2303
  color: str = "primary",
2258
2304
  key: str = "final",
2305
+ preferred_formats: Optional[Union[str, List[str]]] = None,
2259
2306
  ) -> str:
2260
2307
  """
2261
2308
  Generate HTML for a button that allows someone to send the bundle to a
@@ -2264,12 +2311,13 @@ class ALDocumentBundle(DAList):
2264
2311
 
2265
2312
  Args:
2266
2313
  email (str): The recipient's email address.
2267
- editable (bool, optional): Flag indicating if the bundle is editable. Defaults to False.
2314
+ editable (bool, optional): Flag indicating if the bundle is editable. Defaults to False. (deprecated; use preferred_formats instead)
2268
2315
  template_name (str, optional): The name of the template to be used. Defaults to an empty string.
2269
2316
  label (str, optional): The label for the button. Defaults to "Send".
2270
2317
  icon (str, optional): The Fontawesome icon for the button. Defaults to "envelope".
2271
2318
  color (str, optional): The Bootstrap color of the button. Defaults to "primary".
2272
2319
  key (str, optional): A key used to identify which version of the ALDocument to send. Defaults to "final".
2320
+ preferred_formats (Optional[Union[str,List[str]]], optional): A list of allowed formats for the document. Defaults to "pdf" if not specified.
2273
2321
 
2274
2322
  Returns:
2275
2323
  str: The generated HTML string for the button.
@@ -2278,9 +2326,16 @@ class ALDocumentBundle(DAList):
2278
2326
  return "" # Don't let people email an empty set of documents
2279
2327
  if not hasattr(self, "_cached_get_email_copy"):
2280
2328
  self._cached_get_email_copy = str(self.get_email_copy)
2281
- name = html_safe_str(self.instanceName)
2329
+ name = html_safe_str(self.instanceName) + random_suffix()
2282
2330
  al_send_button_id = "al_send_email_to_button_" + name
2283
2331
 
2332
+ if isinstance(preferred_formats, (list, tuple)):
2333
+ formats_js = "[" + ",".join(f"'{fmt}'" for fmt in preferred_formats) + "]"
2334
+ elif preferred_formats:
2335
+ formats_js = f"'{preferred_formats}'"
2336
+ else:
2337
+ formats_js = "null"
2338
+
2284
2339
  javascript_string = (
2285
2340
  f"javascript:aldocument_send_to_action("
2286
2341
  f"'{self.attr_name('send_email_to_action_event')}',"
@@ -2288,7 +2343,9 @@ class ALDocumentBundle(DAList):
2288
2343
  f"'{email}',"
2289
2344
  f"'{al_send_button_id}',"
2290
2345
  f"'{template_name}',"
2291
- f"'{key}')"
2346
+ f"'{key}',"
2347
+ f"{formats_js}"
2348
+ f")"
2292
2349
  )
2293
2350
  send_button = action_button_html(
2294
2351
  javascript_string,
@@ -2309,6 +2366,7 @@ class ALDocumentBundle(DAList):
2309
2366
  template_name: str = "",
2310
2367
  label: str = "Send",
2311
2368
  icon: str = "envelope",
2369
+ preferred_formats: Optional[Union[str, List[str]]] = None,
2312
2370
  ) -> str:
2313
2371
  """
2314
2372
  Generate HTML for an input box and button that allows someone to send the bundle
@@ -2321,12 +2379,13 @@ class ALDocumentBundle(DAList):
2321
2379
  key (str, optional): A key used to identify which version of the ALDocument to send. Defaults to "final".
2322
2380
  show_editable_checkbox (bool, optional): Flag indicating if the checkbox
2323
2381
  for deciding the inclusion of an editable (Word) copy should be displayed.
2324
- Defaults to True.
2382
+ Defaults to True. If preferred_formats = ["pdf"], this will be ignored and no checkbox will be shown.
2325
2383
  template_name (str, optional): Name of the template variable that is used to fill
2326
2384
  the email contents. By default, the `x.send_email_template` template will be used.
2327
2385
  label (str, optional): The label for the button. Defaults to "Send".
2328
2386
  icon (str, optional): The Fontawesome icon for the button. Defaults
2329
2387
  to "envelope".
2388
+ preferred_formats (Optional[Union[str,List[str]]], optional): A list of allowed formats for the document. Defaults to "pdf" if not specified.
2330
2389
 
2331
2390
  Returns:
2332
2391
  str: The generated HTML string for the input box and button.
@@ -2339,31 +2398,52 @@ class ALDocumentBundle(DAList):
2339
2398
  self._cached_include_editable_documents = str(
2340
2399
  self.include_editable_documents
2341
2400
  )
2342
- name = html_safe_str(self.instanceName)
2401
+
2402
+ if isinstance(preferred_formats, str):
2403
+ preferred_formats = [preferred_formats]
2404
+
2405
+ if not preferred_formats:
2406
+ preferred_formats = ["pdf", "docx"] if show_editable_checkbox else ["pdf"]
2407
+
2408
+ name = html_safe_str(self.instanceName) + random_suffix()
2343
2409
  al_wants_editable_input_id = "_ignore_al_wants_editable_" + name
2344
2410
  al_email_input_id = "_ignore_al_doc_email_" + name
2345
2411
  al_send_button_id = "al_send_email_button_" + name
2346
2412
 
2413
+ if isinstance(preferred_formats, (list, tuple)):
2414
+ formats_js = "[" + ",".join(f"'{fmt}'" for fmt in preferred_formats) + "]"
2415
+ elif preferred_formats:
2416
+ formats_js = f"'{preferred_formats}'"
2417
+ else:
2418
+ formats_js = "null"
2419
+
2347
2420
  javascript_string = (
2348
2421
  f"javascript:aldocument_send_action("
2349
2422
  f"'{self.attr_name('send_email_action_event')}',"
2350
2423
  f"'{al_wants_editable_input_id}',"
2351
2424
  f"'{al_email_input_id}',"
2352
2425
  f"'{template_name}',"
2353
- f"'{key}')"
2426
+ f"'{key}',"
2427
+ f"{formats_js}"
2428
+ ")"
2354
2429
  )
2355
2430
 
2356
2431
  # Container of whole email section with header
2357
2432
  return_str = f"""
2358
- <fieldset class="al_send_bundle al_send_section_alone {name}" id="al_send_bundle_{name}" name="al_send_bundle_{name}">
2433
+ <fieldset class="al_send_bundle al_send_section_alone {html_safe_str(self.instanceName)}" id="al_send_bundle_{name}" name="al_send_bundle_{name}">
2359
2434
  <legend class="h4 al_doc_email_header">{self._cached_get_email_copy}</legend>
2360
2435
  """
2361
2436
  # "Editable" checkbox
2362
- if show_editable_checkbox:
2437
+ if (
2438
+ show_editable_checkbox
2439
+ and preferred_formats
2440
+ and isinstance(preferred_formats, list)
2441
+ and len(preferred_formats) > 1
2442
+ ): # Do not need to show if only one valid format allowed
2363
2443
  return_str += f"""
2364
2444
  <div class="form-check-container">
2365
2445
  <div class="form-check">
2366
- <input class="form-check-input" type="checkbox" class="al_wants_editable" id="{al_wants_editable_input_id}">
2446
+ <input class="form-check-input al_wants_editable" type="checkbox" id="{al_wants_editable_input_id}">
2367
2447
  <label class="al_wants_editable form-check-label" for="{al_wants_editable_input_id}">{self._cached_include_editable_documents}
2368
2448
  </label>
2369
2449
  </div>
@@ -2373,7 +2453,7 @@ class ALDocumentBundle(DAList):
2373
2453
  return_str += f"""
2374
2454
  <div class="al_email_container">
2375
2455
 
2376
- <span class="al_email_address {name} container form-group row da-field-container da-field-container-datatype-email">
2456
+ <span class="al_email_address {html_safe_str(self.instanceName)} container form-group row da-field-container da-field-container-datatype-email">
2377
2457
  <label for="{al_email_input_id}" class="col-form-label da-form-label datext-right">Email</label>
2378
2458
  <input value="{user_info().email if user_logged_in() else ''}" alt="Email address for document" class="form-control" type="email" size="35" name="{al_email_input_id}" id="{al_email_input_id}">
2379
2459
  </span>
@@ -2389,8 +2469,9 @@ class ALDocumentBundle(DAList):
2389
2469
  self,
2390
2470
  to: Any = None,
2391
2471
  key: str = "final",
2392
- editable: bool = False,
2393
- template: Any = None,
2472
+ editable: Optional[bool] = None,
2473
+ template: Optional[Any] = None,
2474
+ preferred_formats: Optional[Union[str, List[str]]] = "pdf",
2394
2475
  **kwargs,
2395
2476
  ) -> bool:
2396
2477
  """
@@ -2399,39 +2480,54 @@ class ALDocumentBundle(DAList):
2399
2480
  https://docassemble.org/docs/functions.html#send_email with additional parameters.
2400
2481
 
2401
2482
  Args:
2402
- to (Any): The email address or list of addresses to send to. It can be a string
2403
- or objects with such. Similar to da send_email `to`.
2483
+ to (Any): The email address, list of email addresses, or list of Individuals with a .email attribute to send to.
2404
2484
  key (str, optional): Specifies which version of the document to send. Defaults to "final".
2405
- editable (bool, optional): If True, sends the editable documents. Defaults to False.
2406
- template (Any): The template variable, similar to da `send_email` `template` variable.
2485
+ editable (bool, optional): If True, sends the editable documents. Defaults to False. (Deprecated)
2486
+ template (Any): The template variable for the subject and body of the email, similar to da `send_email` `template` variable.
2487
+ preferred_formats (str): Specifies the format of the files to send. Can be "pdf" or "docx", or a list of these formats. Overrides deprecated `editable` keyword.
2407
2488
  **kwargs: Additional parameters to pass to the da `send_email` function.
2408
2489
 
2409
2490
  Returns:
2410
2491
  bool: Indicates if the email was sent successfully.
2411
2492
  """
2412
- if not template:
2493
+ if editable is not None:
2494
+ log(
2495
+ "The 'editable' parameter is deprecated; use 'preferred_formats' instead."
2496
+ )
2497
+
2498
+ if template is None:
2413
2499
  template = self.send_email_template
2414
2500
 
2415
- if editable:
2416
- return send_email(
2417
- to=to,
2418
- template=template,
2419
- # Add both DOCX and PDF versions, but if it's not possible to be a DOCX don't add the PDF
2420
- # twice
2421
- attachments=list(
2422
- dict.fromkeys(
2423
- self.as_editable_list(key=key) + self.as_pdf_list(key=key)
2424
- )
2425
- ),
2426
- **kwargs,
2427
- )
2428
- else:
2429
- return send_email(
2430
- to=to,
2431
- template=template,
2432
- attachments=self.as_pdf_list(key=key),
2433
- **kwargs,
2434
- )
2501
+ if preferred_formats is None:
2502
+ preferred_formats = ["docx", "pdf"] if editable else ["pdf"]
2503
+
2504
+ if isinstance(preferred_formats, str):
2505
+ preferred_formats = [preferred_formats]
2506
+
2507
+ allowed = {fmt.lower() for fmt in preferred_formats}
2508
+ attachments = []
2509
+
2510
+ for item in self.enabled_documents():
2511
+ primary = None
2512
+
2513
+ if "docx" in allowed:
2514
+ primary = item.as_docx(key=key)
2515
+ attachments.append(primary)
2516
+
2517
+ if "pdf" in allowed:
2518
+ if not (
2519
+ primary
2520
+ and hasattr(primary, "extension")
2521
+ and primary.extension == "pdf"
2522
+ ):
2523
+ attachments.append(item.as_pdf(key=key))
2524
+
2525
+ return send_email(
2526
+ to=to,
2527
+ template=template,
2528
+ attachments=attachments,
2529
+ **kwargs,
2530
+ )
2435
2531
 
2436
2532
  def _is_self_enabled(self, refresh=True) -> bool:
2437
2533
  """