docassemble.AssemblyLine 3.1.0__tar.gz → 3.4.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-3.1.0 → docassemble_assemblyline-3.4.0}/LICENSE +1 -1
  2. {docassemble_assemblyline-3.1.0/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-3.4.0}/PKG-INFO +12 -4
  3. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/__init__.py +1 -0
  4. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/al_courts.py +24 -8
  5. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/al_document.py +277 -70
  6. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/al_general.py +41 -8
  7. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/custom_jinja_filters.py +123 -0
  8. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_code.yml +39 -39
  9. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_document.yml +77 -1
  10. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +1 -1
  11. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +4 -4
  12. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +9 -2
  13. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +37 -16
  14. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +12 -1
  15. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +40 -4
  16. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +53 -24
  17. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +37 -17
  18. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/data/questions/ql_namechange.yml +40 -0
  19. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +42 -15
  20. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml +65 -0
  21. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/data/questions/test_signature.yml +28 -0
  22. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  23. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/language.py +16 -3
  24. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/project_maintenance.py +4 -1
  25. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/requirements.txt +2 -1
  26. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/sessions.py +198 -34
  27. docassemble_assemblyline-3.4.0/docassemble/AssemblyLine/sign.py +144 -0
  28. docassemble_assemblyline-3.4.0/docassemble/__init__.py +1 -0
  29. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +12 -4
  30. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +5 -1
  31. docassemble_assemblyline-3.4.0/docassemble.AssemblyLine.egg-info/requires.txt +2 -0
  32. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble.AssemblyLine.egg-info/top_level.txt +1 -0
  33. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/pyproject.toml +0 -3
  34. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/setup.py +4 -5
  35. docassemble_assemblyline-3.1.0/docassemble/AssemblyLine/__init__.py +0 -1
  36. docassemble_assemblyline-3.1.0/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  37. docassemble_assemblyline-3.1.0/docassemble/__init__.py +0 -2
  38. docassemble_assemblyline-3.1.0/docassemble.AssemblyLine.egg-info/namespace_packages.txt +0 -1
  39. docassemble_assemblyline-3.1.0/docassemble.AssemblyLine.egg-info/requires.txt +0 -2
  40. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/MANIFEST.in +0 -0
  41. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/README.md +0 -0
  42. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_language.yml +0 -0
  43. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  44. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  45. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +0 -0
  46. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +0 -0
  47. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  48. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +0 -0
  49. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  50. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/feedback.yml +0 -0
  51. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -0
  52. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  53. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  54. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +0 -0
  55. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  56. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  57. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  58. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  59. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +0 -0
  60. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  61. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/es-words.yml +0 -0
  62. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  63. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  64. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  65. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  66. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  67. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  68. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +0 -0
  69. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  70. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  71. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  72. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  73. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  74. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  75. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/aldocument.js +0 -0
  76. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  77. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  78. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  79. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  80. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  81. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  82. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  83. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  84. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  85. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  86. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  87. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  88. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  89. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  90. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  91. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/py.typed +0 -0
  92. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/test_al_document.py +0 -0
  93. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/test_al_general.py +0 -0
  94. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble/AssemblyLine/test_language.py +0 -0
  95. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  96. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/docassemble.AssemblyLine.egg-info/not-zip-safe +0 -0
  97. {docassemble_assemblyline-3.1.0 → docassemble_assemblyline-3.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2023 Suffolk Legal Innovation and Technology Lab
3
+ Copyright (c) 2025 Suffolk Legal Innovation and Technology Lab
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,14 +1,22 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 3.1.0
3
+ Version: 3.4.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.8.3
11
- Requires-Dist: docassemble.GithubFeedbackForm>=0.2.1
10
+ Requires-Dist: docassemble.ALToolbox>=0.11.1
11
+ Requires-Dist: docassemble.GithubFeedbackForm>=0.4.1.1
12
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: description
15
+ Dynamic: description-content-type
16
+ Dynamic: home-page
17
+ Dynamic: license
18
+ Dynamic: license-file
19
+ Dynamic: requires-dist
12
20
 
13
21
  # Suffolk LIT Lab Document Assembly Line
14
22
 
@@ -0,0 +1 @@
1
+ __version__ = '3.4.0'
@@ -24,19 +24,28 @@ __all__ = [
24
24
 
25
25
 
26
26
  class ALCourt(Court):
27
- """Object representing a court in Massachusetts.
28
- TODO: it could be interesting to store a jurisdiction on a court. But this is non-trivial. Should it be geo boundaries?
27
+ """Object representing a court in Massachusetts. We use a function on the CourtList object that filters courts by
28
+ address and can use any of those three features of the court to do the filtering.
29
+
30
+ <!-- TODO: it could be interesting to store a jurisdiction on a court. But this is non-trivial. Should it be geo boundaries?
29
31
  A list of cities? A list of counties? Instead, we use a function on the CourtList object that filters courts by
30
- address and can use any of those three features of the court to do the filtering."""
32
+ address and can use any of those three features of the court to do the filtering.-->
33
+ """
31
34
 
32
- def init(self, *pargs, **kwargs):
35
+ def init(self, *pargs, **kwargs) -> None:
36
+ """Create a new court object.
37
+
38
+ Args:
39
+ *pargs: Standard DAObject positional arguments
40
+ **kwargs: Standard DAObject keyword arguments
41
+ """
33
42
  super().init(*pargs, **kwargs)
34
43
  if "address" not in kwargs:
35
44
  self.initializeAttribute("address", ALAddress)
36
45
  if (
37
46
  "jurisdiction" not in kwargs
38
47
  ): # This attribute isn't used. Could be a better way to handle court locating
39
- self.jurisdiction = []
48
+ self.jurisdiction: list = []
40
49
  if "location" not in kwargs:
41
50
  self.initializeAttribute("location", LatitudeLongitude)
42
51
 
@@ -208,7 +217,13 @@ class ALCourtLoader(DAObject):
208
217
  converters (Dict[str, Callable]): A dictionary of functions to apply to columns in the dataframe.
209
218
  """
210
219
 
211
- def init(self, *pargs, **kwargs):
220
+ def init(self, *pargs, **kwargs) -> None:
221
+ """Create a new courtloader object.
222
+
223
+ Args:
224
+ *pargs: Standard DAObject positional arguments
225
+ **kwargs: Standard DAObject keyword arguments
226
+ """
212
227
  super().init(*pargs, **kwargs)
213
228
  self.package = docassemble.base.functions.this_thread.current_question.package
214
229
  if not hasattr(self, "filename"):
@@ -240,8 +255,9 @@ class ALCourtLoader(DAObject):
240
255
  column_name (str): The name of the column in the dataframe.
241
256
 
242
257
  Returns:
243
- Set[str]: A set containing unique values from the specified column.
244
- Returns an empty set if an error occurs.
258
+ Set[str]:
259
+ - A set containing unique values from the specified column.
260
+ - Returns an empty set if the column does not exist or an error occurs.
245
261
  """
246
262
  df = self._load_courts()
247
263
  try:
@@ -35,6 +35,7 @@ from math import floor
35
35
  import subprocess
36
36
  from collections import ChainMap
37
37
  import pikepdf
38
+ from typing import Tuple
38
39
 
39
40
  __all__ = [
40
41
  "ALAddendumField",
@@ -247,7 +248,13 @@ class ALAddendumField(DAObject):
247
248
  The attributes `headers` and `field_style` are planned for future releases and are not currently implemented.
248
249
  """
249
250
 
250
- def init(self, *pargs, **kwargs):
251
+ def init(self, *pargs, **kwargs) -> None:
252
+ """Standard DAObject init method.
253
+
254
+ Args:
255
+ *pargs: Positional arguments
256
+ **kwargs: Keyword arguments
257
+ """
251
258
  super().init(*pargs, **kwargs)
252
259
 
253
260
  def overflow_value(
@@ -751,7 +758,13 @@ class ALAddendumFieldDict(DAOrderedDict):
751
758
  only the overflow text will be displayed.
752
759
  """
753
760
 
754
- def init(self, *pargs, **kwargs):
761
+ def init(self, *pargs, **kwargs) -> None:
762
+ """Standard DAObject init method.
763
+
764
+ Args:
765
+ *pargs: Positional arguments
766
+ **kwargs: Keyword arguments
767
+ """
755
768
  super(ALAddendumFieldDict, self).init(*pargs, **kwargs)
756
769
  self.object_type = ALAddendumField
757
770
  self.auto_gather = False
@@ -989,7 +1002,13 @@ class ALDocument(DADict):
989
1002
  ```
990
1003
  """
991
1004
 
992
- def init(self, *pargs, **kwargs):
1005
+ def init(self, *pargs, **kwargs) -> None:
1006
+ """Standard DAObject init method.
1007
+
1008
+ Args:
1009
+ *pargs: Positional arguments
1010
+ **kwargs: Keyword arguments
1011
+ """
993
1012
  super(ALDocument, self).init(*pargs, **kwargs)
994
1013
  self.initializeAttribute("overflow_fields", ALAddendumFieldDict)
995
1014
  if not hasattr(self, "default_overflow_message"):
@@ -1344,7 +1363,13 @@ class ALStaticDocument(DAStaticFile):
1344
1363
  Consider handling files in `/data/templates` if deemed useful, potentially by copying into a DAFile using `pdf_concatenate()`.
1345
1364
  """
1346
1365
 
1347
- def init(self, *pargs, **kwargs):
1366
+ def init(self, *pargs, **kwargs) -> None:
1367
+ """Standard DAObject init method.
1368
+
1369
+ Args:
1370
+ *pargs: Positional arguments
1371
+ **kwargs: Keyword arguments
1372
+ """
1348
1373
  super().init(*pargs, **kwargs)
1349
1374
  self.has_addendum = False
1350
1375
  self.auto_gather = False
@@ -1514,7 +1539,13 @@ class ALDocumentBundle(DAList):
1514
1539
  ```
1515
1540
  """
1516
1541
 
1517
- def init(self, *pargs, **kwargs):
1542
+ def init(self, *pargs, **kwargs) -> None:
1543
+ """Standard DAObject init method.
1544
+
1545
+ Args:
1546
+ *pargs: Positional arguments
1547
+ **kwargs: Keyword arguments
1548
+ """
1518
1549
  super().init(*pargs, **kwargs)
1519
1550
  if "auto_gather" not in kwargs:
1520
1551
  self.auto_gather = False
@@ -1822,6 +1853,107 @@ class ALDocumentBundle(DAList):
1822
1853
  editable.append(doc)
1823
1854
  return editable
1824
1855
 
1856
+ def get_cacheable_documents(
1857
+ self,
1858
+ key: str = "final",
1859
+ pdf: bool = True,
1860
+ docx: bool = False,
1861
+ original: bool = False,
1862
+ refresh: bool = True,
1863
+ pdfa: bool = False,
1864
+ include_zip: bool = True,
1865
+ include_full_pdf: bool = False,
1866
+ append_matching_suffix: bool = True,
1867
+ ) -> Tuple[List[Dict[str, DAFile]], Optional[DAFile], Optional[DAFile]]:
1868
+ """
1869
+ Generates a cache of all enabled documents in the bundle, and returns it in a structure that can be cached
1870
+ and returned for use in a background process.
1871
+
1872
+ The result looks like this:
1873
+
1874
+ [
1875
+ {"title": "Document 1", "pdf": DAFile, "docx": DAFile, download_filename: str},
1876
+ ],
1877
+ DAFile, # Zip of whole bundle
1878
+ DAFile # PDF of whole bundle
1879
+
1880
+ Args:
1881
+ key (str): Identifier for the document version, default is "final".
1882
+ pdf (bool): Flag to include a PDF version of each file, default is True.
1883
+ docx (bool): Flag to include a DOCX version of each file, default is False.
1884
+ original (bool): Flag to include the original version of each file, default is False.
1885
+ refresh (bool): Flag to reconsider the 'enabled' attribute, default is True.
1886
+ pdfa (bool): Flag to return documents in PDF/A format, default is False.
1887
+ include_zip (bool): Flag to include a zip option, default is True.
1888
+ include_full_pdf (bool): Flag to include a PDF version of the whole bundle, default is False.
1889
+ append_matching_suffix (bool): Flag to determine if matching suffix should be appended to file name, default is True.
1890
+
1891
+ Returns:
1892
+ 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
1893
+ """
1894
+ # reduce idempotency delays
1895
+ enabled_docs = self.enabled_documents(refresh=refresh)
1896
+ for doc in enabled_docs:
1897
+ doc.title
1898
+
1899
+ results = []
1900
+
1901
+ for doc in enabled_docs:
1902
+ result = {"title": doc.title}
1903
+ filename_root = os.path.splitext(str(doc.filename))[0]
1904
+ if pdf:
1905
+ result["pdf"] = doc.as_pdf(
1906
+ key=key,
1907
+ refresh=refresh,
1908
+ pdfa=pdfa,
1909
+ append_matching_suffix=append_matching_suffix,
1910
+ )
1911
+ result["download_filename"] = filename_root + ".pdf"
1912
+ if docx and doc._is_docx(key=key):
1913
+ result["docx"] = doc.as_docx(
1914
+ key=key,
1915
+ refresh=refresh,
1916
+ append_matching_suffix=append_matching_suffix,
1917
+ )
1918
+ result["download_filename"] = filename_root + ".docx"
1919
+ if original:
1920
+ result["original"] = doc[key]
1921
+ result["download_filename"] = doc.filename
1922
+
1923
+ try:
1924
+ # If it's possible, set the file extension to the actual filetype
1925
+ # This is mostly necessary if people omit the file extension in attachment block
1926
+ # for filetype="original"
1927
+ if original:
1928
+ download_doc = result["original"]
1929
+ else:
1930
+ download_doc = result["docx"]
1931
+ ext = next(
1932
+ iter(
1933
+ mimetypes.guess_all_extensions(
1934
+ download_doc.mimetype, strict=True
1935
+ )
1936
+ )
1937
+ )
1938
+ result["download_filename"] = filename_root + ext
1939
+ except:
1940
+ pass
1941
+ results.append(result)
1942
+
1943
+ if len(enabled_docs) > 1 and include_zip:
1944
+ bundled_zip = self.as_zip(
1945
+ key=key, format="original" if original else "docx" if docx else "pdf"
1946
+ )
1947
+ else:
1948
+ bundled_zip = None
1949
+
1950
+ if len(enabled_docs) > 1 and include_full_pdf:
1951
+ bundled_pdf = self.as_pdf(key=key, pdfa=pdfa)
1952
+ else:
1953
+ bundled_pdf = None
1954
+
1955
+ return results, bundled_zip, bundled_pdf
1956
+
1825
1957
  def download_list_html(
1826
1958
  self,
1827
1959
  key: str = "final",
@@ -1834,10 +1966,16 @@ class ALDocumentBundle(DAList):
1834
1966
  view_icon: str = "eye",
1835
1967
  download_label: str = "Download",
1836
1968
  download_icon: str = "download",
1969
+ send_label: str = "Send",
1970
+ send_icon: str = "envelope",
1837
1971
  zip_label: Optional[str] = None,
1838
1972
  zip_icon: str = "file-archive",
1973
+ zip_row_label: Optional[str] = "",
1839
1974
  append_matching_suffix: bool = True,
1840
1975
  include_email: bool = False,
1976
+ use_previously_cached_files: bool = False,
1977
+ include_full_pdf: bool = False,
1978
+ full_pdf_label: Optional[str] = None,
1841
1979
  ) -> str:
1842
1980
  """
1843
1981
  Constructs an HTML table displaying a list of documents with 'view' and 'download' buttons.
@@ -1853,10 +1991,17 @@ class ALDocumentBundle(DAList):
1853
1991
  view_icon (str): Icon for the 'view' button, default is "eye".
1854
1992
  download_label (str): Label for the 'download' button, default is "Download".
1855
1993
  download_icon (str): Icon for the 'download' button, default is "download".
1994
+ send_label (str): Label for the 'send' button. Default is "Send".
1995
+ send_icon (str): Fontawesome icon for the 'send' button. Default is "envelope".
1856
1996
  zip_label (Optional[str]): Label for the zip option. If not provided, uses the generic template for `self.zip_label` ("Download all").
1857
1997
  zip_icon (str): Icon for the zip option, default is "file-archive".
1998
+ zip_row_label (str, optional): Text to go in the left-most column
1999
+ of the table's zip row. Will default to the value of `self.title`.
1858
2000
  append_matching_suffix (bool): Flag to determine if matching suffix should be appended to file name, default is True.
1859
- include_email (bool): Flag to include an email option, default is False.
2001
+ include_email (bool): Flag to include an option, default is False.
2002
+ use_previously_cached_files (bool): Flag to use previously cached files (e.g., made in background) if defined. default is False.
2003
+ include_full_pdf (bool): Flag to include a full PDF option, default is False.
2004
+ 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").
1860
2005
 
1861
2006
  Returns:
1862
2007
  str: HTML representation of a table with documents and their associated actions.
@@ -1864,52 +2009,40 @@ class ALDocumentBundle(DAList):
1864
2009
  if not hasattr(self, "_cached_zip_label"):
1865
2010
  self._cached_zip_label = str(self.zip_label)
1866
2011
 
1867
- # Trigger some variables up top to avoid idempotency issues
1868
- enabled_docs = self.enabled_documents(refresh=refresh)
1869
- for doc in enabled_docs:
1870
- doc.title
1871
- if format == "pdf":
1872
- doc.as_pdf(
1873
- key=key,
1874
- refresh=refresh,
1875
- pdfa=pdfa,
1876
- append_matching_suffix=append_matching_suffix,
1877
- ) # Generate cached file for this session
2012
+ if not hasattr(self, "_cached_full_pdf_label"):
2013
+ self._cached_full_pdf_label = str(self.full_pdf_label)
2014
+
2015
+ if use_previously_cached_files and hasattr(self, "_downloadable_files"):
2016
+ downloadable_files, bundled_zip, bundled_pdf = self._downloadable_files
2017
+ else:
2018
+ downloadable_files, bundled_zip, bundled_pdf = self.get_cacheable_documents(
2019
+ key=key,
2020
+ pdf=(format == "pdf" or view == True),
2021
+ docx=format == "docx",
2022
+ original=format == "original",
2023
+ refresh=refresh,
2024
+ pdfa=pdfa,
2025
+ include_zip=include_zip,
2026
+ include_full_pdf=include_full_pdf,
2027
+ append_matching_suffix=append_matching_suffix,
2028
+ )
1878
2029
 
1879
2030
  html = f'<div class="container al_table al_doc_table" id="{ html_safe_str(self.instanceName) }">'
1880
2031
 
1881
- for doc in enabled_docs:
1882
- filename_root = os.path.splitext(str(doc.filename))[0]
1883
- # Do our best to use the provided filename + the extension from requested filetype
1884
- if format == "original":
1885
- download_doc = doc[key]
1886
- download_filename = doc.filename
1887
- if format == "docx" and doc._is_docx(key=key):
1888
- download_doc = doc.as_docx(
1889
- key=key, append_matching_suffix=append_matching_suffix
1890
- )
1891
- download_filename = filename_root + ".docx"
1892
- else:
1893
- download_doc = doc.as_pdf(
1894
- key=key, append_matching_suffix=append_matching_suffix
1895
- )
1896
- download_filename = filename_root + ".pdf"
2032
+ for result in downloadable_files:
2033
+ title = result["title"]
2034
+ download_filename = result.get("download_filename", "document")
1897
2035
 
1898
- try:
1899
- # If it's possible, set the file extension to the actual filetype
1900
- # This is mostly necessary if people omit the file extension in attachment block
1901
- # for filetype="original"
1902
- ext = next(
1903
- iter(
1904
- mimetypes.guess_all_extensions(
1905
- download_doc.mimetype, strict=True
1906
- )
1907
- )
1908
- )
1909
- download_filename = filename_root + ext
1910
- except:
1911
- pass
2036
+ if format == "original" and "original" in result:
2037
+ download_doc = result["original"]
2038
+ elif format == "docx" and "docx" in result:
2039
+ download_doc = result["docx"]
2040
+ elif "pdf" in result:
2041
+ download_doc = result["pdf"]
2042
+ else:
2043
+ continue # Skip if the desired format is not available
1912
2044
 
2045
+ # Construct the download button
1913
2046
  doc_download_button = action_button_html(
1914
2047
  download_doc.url_for(
1915
2048
  attachment=True, display_filename=download_filename
@@ -1920,12 +2053,14 @@ class ALDocumentBundle(DAList):
1920
2053
  size="md",
1921
2054
  classname="al_download al_button",
1922
2055
  )
1923
- if view and doc.as_pdf().url_for().endswith(".pdf"):
2056
+
2057
+ # Construct the view button if needed
2058
+ if view and "pdf" in result and result["pdf"].url_for().endswith(".pdf"):
2059
+ # Use .pdf as the filename extension
2060
+ view_filename = os.path.splitext(download_filename)[0] + ".pdf"
1924
2061
  doc_view_button = action_button_html(
1925
- doc.as_pdf(key=key).url_for(
1926
- attachment=False,
1927
- display_filename=filename_root + ".pdf",
1928
- append_matching_suffix=append_matching_suffix,
2062
+ result["pdf"].url_for(
2063
+ attachment=False, display_filename=view_filename
1929
2064
  ),
1930
2065
  label=view_label,
1931
2066
  icon=view_icon,
@@ -1936,31 +2071,52 @@ class ALDocumentBundle(DAList):
1936
2071
  buttons = [doc_view_button, doc_download_button]
1937
2072
  else:
1938
2073
  buttons = [doc_download_button]
1939
- html += table_row(doc.title, buttons)
1940
2074
 
1941
- # Add a zip file row if there's more than one doc
1942
- filename_root = os.path.splitext(str(self.filename))[0]
1943
- if len(enabled_docs) > 1 and include_zip:
2075
+ html += table_row(title, buttons)
2076
+
2077
+ # Add a zip file row if included
2078
+ if include_zip and bundled_zip:
1944
2079
  if not zip_label:
1945
2080
  zip_label = self._cached_zip_label
1946
- # Zip file will match the format of the download table
1947
- zip = self.as_zip(key=key, format=format, include_pdf=view)
2081
+ filename_root = os.path.splitext(str(self.filename))[0]
1948
2082
  zip_button = action_button_html(
1949
- zip.url_for(attachment=False, display_filename=filename_root + ".zip"),
2083
+ bundled_zip.url_for(
2084
+ attachment=False, display_filename=filename_root + ".zip"
2085
+ ),
1950
2086
  label=zip_label,
1951
2087
  icon=zip_icon,
1952
2088
  color="primary",
1953
2089
  size="md",
1954
2090
  classname="al_zip al_button",
1955
2091
  )
1956
- html += table_row(zip.title, zip_button)
2092
+ safe_zip_row_label: str = (
2093
+ zip_row_label or (hasattr(self, "title") and self.title) or zip_label
2094
+ )
2095
+ html += table_row(safe_zip_row_label, zip_button)
2096
+
2097
+ if include_full_pdf and bundled_pdf:
2098
+ if not full_pdf_label:
2099
+ full_pdf_label = self._cached_full_pdf_label
2100
+ filename_root = os.path.splitext(str(self.filename))[0]
2101
+ full_pdf_button = action_button_html(
2102
+ bundled_pdf.url_for(
2103
+ attachment=False, display_filename=filename_root + ".pdf"
2104
+ ),
2105
+ label=full_pdf_label,
2106
+ icon="file-pdf",
2107
+ color="primary",
2108
+ size="md",
2109
+ classname="al_full_pdf al_button",
2110
+ )
2111
+ html += table_row(full_pdf_label, full_pdf_button)
1957
2112
 
1958
2113
  if include_email:
1959
- html += self.send_email_table_row(key=key)
2114
+ html += self.send_email_table_row(
2115
+ key=key, send_label=send_label, send_icon=send_icon
2116
+ )
1960
2117
 
1961
2118
  html += "\n</div>"
1962
2119
 
1963
- # Discuss: Do we want a table with the ability to have a merged pdf row?
1964
2120
  return html
1965
2121
 
1966
2122
  def download_html(
@@ -2036,13 +2192,17 @@ class ALDocumentBundle(DAList):
2036
2192
 
2037
2193
  return html
2038
2194
 
2039
- def send_email_table_row(self, key: str = "final") -> str:
2195
+ def send_email_table_row(
2196
+ self, key: str = "final", send_label: str = "Send", send_icon: str = "envelope"
2197
+ ) -> str:
2040
2198
  """
2041
2199
  Generate HTML doc table row for an input box and button that allows
2042
2200
  someone to send the bundle to the specified email address.
2043
2201
 
2044
2202
  Args:
2045
2203
  key (str): A key used to identify which version of the ALDocument to send. Defaults to "final".
2204
+ send_label (str): Label for the 'send' button. Default is "Send".
2205
+ send_icon (str): Icon for the 'send' button. Default is "envelope".
2046
2206
 
2047
2207
  Returns:
2048
2208
  str: The generated HTML string for the table row.
@@ -2075,7 +2235,7 @@ class ALDocumentBundle(DAList):
2075
2235
  """
2076
2236
 
2077
2237
  # "Send" button for the 2nd column of the table row
2078
- send_button = f'{action_button_html(javascript_string, label="Send", icon="envelope", color="primary", size="md", classname="al_send_email_button al_button", id_tag=al_send_button_id)}'
2238
+ send_button = f'{action_button_html(javascript_string, label=send_label, icon=send_icon, color="primary", size="md", classname="al_send_email_button al_button", id_tag=al_send_button_id)}'
2079
2239
 
2080
2240
  # Whole row put together
2081
2241
  html = f"""
@@ -2147,6 +2307,8 @@ class ALDocumentBundle(DAList):
2147
2307
  key: str = "final",
2148
2308
  show_editable_checkbox: bool = True,
2149
2309
  template_name: str = "",
2310
+ label: str = "Send",
2311
+ icon: str = "envelope",
2150
2312
  ) -> str:
2151
2313
  """
2152
2314
  Generate HTML for an input box and button that allows someone to send the bundle
@@ -2162,6 +2324,9 @@ class ALDocumentBundle(DAList):
2162
2324
  Defaults to True.
2163
2325
  template_name (str, optional): Name of the template variable that is used to fill
2164
2326
  the email contents. By default, the `x.send_email_template` template will be used.
2327
+ label (str, optional): The label for the button. Defaults to "Send".
2328
+ icon (str, optional): The Fontawesome icon for the button. Defaults
2329
+ to "envelope".
2165
2330
 
2166
2331
  Returns:
2167
2332
  str: The generated HTML string for the input box and button.
@@ -2213,7 +2378,7 @@ class ALDocumentBundle(DAList):
2213
2378
  <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}">
2214
2379
  </span>
2215
2380
 
2216
- {action_button_html(javascript_string, label="Send", icon="envelope", color="primary", size="md", classname="al_send_email_button", id_tag=al_send_button_id)}
2381
+ {action_button_html(javascript_string, label=label, icon=icon, color="primary", size="md", classname="al_send_email_button", id_tag=al_send_button_id)}
2217
2382
 
2218
2383
  </div>
2219
2384
  """
@@ -2382,7 +2547,13 @@ class ALExhibit(DAObject):
2382
2547
  starting_page (int): first page number to use in table of contents
2383
2548
  """
2384
2549
 
2385
- def init(self, *pargs, **kwargs):
2550
+ def init(self, *pargs, **kwargs) -> None:
2551
+ """Standard DAObject init method.
2552
+
2553
+ Args:
2554
+ *pargs: Positional arguments
2555
+ **kwargs: Keyword arguments
2556
+ """
2386
2557
  super().init(*pargs, **kwargs)
2387
2558
  self.initializeAttribute("_cache", DALazyAttribute)
2388
2559
  self.object_type = DAFileList
@@ -2602,7 +2773,13 @@ class ALExhibitList(DAList):
2602
2773
  auto_ocr (bool): If True, automatically starts OCR processing for uploaded exhibits. Defaults to True.
2603
2774
  """
2604
2775
 
2605
- def init(self, *pargs, **kwargs):
2776
+ def init(self, *pargs, **kwargs) -> None:
2777
+ """Standard DAObject init method.
2778
+
2779
+ Args:
2780
+ *pargs: Positional arguments
2781
+ **kwargs: Keyword arguments
2782
+ """
2606
2783
  super().init(*pargs, **kwargs)
2607
2784
  if not hasattr(self, "auto_label"):
2608
2785
  self.auto_label = True
@@ -2780,7 +2957,25 @@ class ALExhibitDocument(ALDocument):
2780
2957
  ```
2781
2958
  """
2782
2959
 
2783
- def init(self, *pargs, **kwargs):
2960
+ include_exhibit_cover_pages: bool
2961
+ include_table_of_contents: bool
2962
+ add_page_numbers: bool
2963
+ has_addendum: bool
2964
+ auto_labeler: Callable
2965
+ auto_ocr: bool
2966
+ bates_prefix: str
2967
+ maximum_size: int
2968
+ suffix_to_append: str
2969
+ exhibits: ALExhibitList
2970
+ table_of_contents: DAFile
2971
+
2972
+ def init(self, *pargs, **kwargs) -> None:
2973
+ """Standard DAObject init method.
2974
+
2975
+ Args:
2976
+ *pargs: Positional arguments
2977
+ **kwargs: Keyword arguments
2978
+ """
2784
2979
  super().init(*pargs, **kwargs)
2785
2980
  self.initializeAttribute("exhibits", ALExhibitList)
2786
2981
  if hasattr(self, "auto_labeler"):
@@ -2931,7 +3126,13 @@ class ALTableDocument(ALDocument):
2931
3126
  table (???): Represents the actual table data. Type and attributes need more context to document.
2932
3127
  """
2933
3128
 
2934
- def init(self, *pargs, **kwargs):
3129
+ def init(self, *pargs, **kwargs) -> None:
3130
+ """Standard DAObject init method.
3131
+
3132
+ Args:
3133
+ *pargs: Positional arguments
3134
+ **kwargs: Keyword arguments
3135
+ """
2935
3136
  super().init(*pargs, **kwargs)
2936
3137
  self.has_addendum = False
2937
3138
  if not hasattr(self, "suffix_to_append"):
@@ -3042,7 +3243,13 @@ class ALUntransformedDocument(ALDocument):
3042
3243
  suffix_to_append (str): Suffix that can be appended to file names, defaulting to "preview".
3043
3244
  """
3044
3245
 
3045
- def init(self, *pargs, **kwargs):
3246
+ def init(self, *pargs, **kwargs) -> None:
3247
+ """Standard DAObject init method.
3248
+
3249
+ Args:
3250
+ *pargs: Positional arguments
3251
+ **kwargs: Keyword arguments
3252
+ """
3046
3253
  super().init(*pargs, **kwargs)
3047
3254
  self.has_addendum = False
3048
3255
  if not hasattr(self, "suffix_to_append"):