docassemble.AssemblyLine 4.6.3__tar.gz → 4.7.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.6.3/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-4.7.0}/PKG-INFO +2 -2
  2. docassemble_assemblyline-4.7.0/docassemble/AssemblyLine/__init__.py +1 -0
  3. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/al_document.py +40 -36
  4. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +1 -1
  5. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +1 -1
  6. docassemble_assemblyline-4.7.0/docassemble/AssemblyLine/data/questions/test_saving_snapshots.yml +17 -0
  7. docassemble_assemblyline-4.7.0/docassemble/AssemblyLine/data/sources/test_snapshots.feature +10 -0
  8. docassemble_assemblyline-4.7.0/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  9. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/sessions.py +82 -54
  10. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/test_al_document.py +30 -1
  11. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +2 -2
  12. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +2 -0
  13. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble.AssemblyLine.egg-info/requires.txt +1 -1
  14. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/pyproject.toml +4 -2
  15. docassemble_assemblyline-4.6.3/docassemble/AssemblyLine/__init__.py +0 -1
  16. docassemble_assemblyline-4.6.3/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  17. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/LICENSE +0 -0
  18. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/MANIFEST.in +0 -0
  19. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/README.md +0 -0
  20. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/al_courts.py +0 -0
  21. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/al_general.py +0 -0
  22. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/custom_jinja_filters.py +0 -0
  23. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_code.yml +0 -0
  24. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_document.yml +0 -0
  25. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_language.yml +0 -0
  26. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  27. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  28. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +0 -0
  29. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +0 -0
  30. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +0 -0
  31. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +0 -0
  32. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  33. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +0 -0
  34. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +0 -0
  35. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  36. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +0 -0
  37. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/feedback.yml +0 -0
  38. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +0 -0
  39. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +0 -0
  40. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/ql_namechange.yml +0 -0
  41. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -0
  42. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  43. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  44. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +0 -0
  45. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +0 -0
  46. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml +0 -0
  47. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  48. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  49. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  50. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_catchall.yml +0 -0
  51. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  52. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_if_final.yml +0 -0
  53. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_preferred_formats.yml +0 -0
  54. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_preferred_name.yml +0 -0
  55. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +0 -0
  56. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/questions/test_signature.yml +0 -0
  57. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  58. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/ar-words.yml +0 -0
  59. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/es-words.yml +0 -0
  60. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/fr-words.yml +0 -0
  61. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  62. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/ne-words.yml +0 -0
  63. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  64. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  65. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  66. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  67. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  68. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +0 -0
  69. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  70. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  71. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  72. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  73. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  74. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  75. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  76. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/aldocument.js +0 -0
  77. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  78. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  79. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  80. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  81. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  82. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  83. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  84. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  85. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  86. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  87. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  88. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  89. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  90. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  91. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/test_catchall.docx +0 -0
  92. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/data/templates/test_if_final.docx +0 -0
  93. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/language.py +0 -0
  94. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/py.typed +0 -0
  95. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/sign.py +0 -0
  96. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/test_al_general.py +0 -0
  97. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/test_all_imports.py +0 -0
  98. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble/AssemblyLine/test_language.py +0 -0
  99. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  100. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/docassemble.AssemblyLine.egg-info/top_level.txt +0 -0
  101. {docassemble_assemblyline-4.6.3 → docassemble_assemblyline-4.7.0}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 4.6.3
3
+ Version: 4.7.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
  Author-email: Suffolk Legal Innovation and Technology Lab <litlab@suffolk.edu>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://courtformsonline.org
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: docassemble.ALToolbox>=0.16.0
10
+ Requires-Dist: docassemble.ALToolbox>=0.18.0
11
11
  Requires-Dist: docassemble.GitHubFeedbackForm>=0.4.1.1
12
12
  Dynamic: license-file
13
13
 
@@ -0,0 +1 @@
1
+ __version__ = '4.7.0'
@@ -1,6 +1,8 @@
1
- import re
2
- import os
1
+ import json
3
2
  import mimetypes
3
+ import os
4
+ import re
5
+ from html import escape
4
6
  from typing import Any, Dict, List, Literal, Union, Callable, Optional
5
7
  from docassemble.base.util import (
6
8
  Address,
@@ -175,6 +177,14 @@ def html_safe_str(the_string: str) -> str:
175
177
  return re.sub(r"[^A-Za-z0-9]+", "_", the_string)
176
178
 
177
179
 
180
+ def _javascript_href(function_name: str, *args: Any) -> str:
181
+ """Build a JavaScript URL that is safe to use in an HTML href attribute."""
182
+ return escape(
183
+ f"javascript:{function_name}({','.join(json.dumps(arg) for arg in args)})",
184
+ quote=True,
185
+ )
186
+
187
+
178
188
  def table_row(title: str, button_htmls: List[str] = []) -> str:
179
189
  """
180
190
  Generate an HTML row string for an AL document-styled table.
@@ -2408,10 +2418,11 @@ class ALDocumentBundle(DAList):
2408
2418
  al_email_input_id = "_ignore_al_doc_email_" + name
2409
2419
  al_send_button_id = "al_send_email_button_" + name
2410
2420
 
2411
- javascript_string = (
2412
- f"javascript:aldocument_send_action("
2413
- f"'{self.attr_name('send_email_action_event')}',"
2414
- f"'{al_wants_editable_input_id}','{al_email_input_id}')"
2421
+ javascript_string = _javascript_href(
2422
+ "aldocument_send_action",
2423
+ str(self.attr_name("send_email_action_event")),
2424
+ al_wants_editable_input_id,
2425
+ al_email_input_id,
2415
2426
  )
2416
2427
 
2417
2428
  # Label "email" and input field for the 1st column of the table row
@@ -2474,23 +2485,23 @@ class ALDocumentBundle(DAList):
2474
2485
  name = html_safe_str(self.instanceName) + random_suffix()
2475
2486
  al_send_button_id = "al_send_email_to_button_" + name
2476
2487
 
2488
+ formats_value: Optional[Union[str, List[str]]]
2477
2489
  if isinstance(preferred_formats, (list, tuple)):
2478
- formats_js = "[" + ",".join(f"'{fmt}'" for fmt in preferred_formats) + "]"
2490
+ formats_value = [str(fmt) for fmt in preferred_formats]
2479
2491
  elif preferred_formats:
2480
- formats_js = f"'{preferred_formats}'"
2492
+ formats_value = str(preferred_formats)
2481
2493
  else:
2482
- formats_js = "null"
2483
-
2484
- javascript_string = (
2485
- f"javascript:aldocument_send_to_action("
2486
- f"'{self.attr_name('send_email_to_action_event')}',"
2487
- f"'{editable}',"
2488
- f"'{email}',"
2489
- f"'{al_send_button_id}',"
2490
- f"'{template_name}',"
2491
- f"'{key}',"
2492
- f"{formats_js}"
2493
- f")"
2494
+ formats_value = None
2495
+
2496
+ javascript_string = _javascript_href(
2497
+ "aldocument_send_to_action",
2498
+ str(self.attr_name("send_email_to_action_event")),
2499
+ str(editable),
2500
+ str(email),
2501
+ al_send_button_id,
2502
+ str(template_name),
2503
+ str(key),
2504
+ formats_value,
2494
2505
  )
2495
2506
  send_button = action_button_html(
2496
2507
  javascript_string,
@@ -2560,22 +2571,15 @@ class ALDocumentBundle(DAList):
2560
2571
  al_email_input_id = "_ignore_al_doc_email_" + name
2561
2572
  al_send_button_id = "al_send_email_button_" + name
2562
2573
 
2563
- if isinstance(preferred_formats, (list, tuple)):
2564
- formats_js = "[" + ",".join(f"'{fmt}'" for fmt in preferred_formats) + "]"
2565
- elif preferred_formats:
2566
- formats_js = f"'{preferred_formats}'"
2567
- else:
2568
- formats_js = "null"
2569
-
2570
- javascript_string = (
2571
- f"javascript:aldocument_send_action("
2572
- f"'{self.attr_name('send_email_action_event')}',"
2573
- f"'{al_wants_editable_input_id}',"
2574
- f"'{al_email_input_id}',"
2575
- f"'{template_name}',"
2576
- f"'{key}',"
2577
- f"{formats_js}"
2578
- ")"
2574
+ formats_value = [str(fmt) for fmt in preferred_formats]
2575
+ javascript_string = _javascript_href(
2576
+ "aldocument_send_action",
2577
+ str(self.attr_name("send_email_action_event")),
2578
+ al_wants_editable_input_id,
2579
+ al_email_input_id,
2580
+ str(template_name),
2581
+ str(key),
2582
+ formats_value,
2579
2583
  )
2580
2584
 
2581
2585
  # Container of whole email section with header
@@ -195,7 +195,7 @@ code: |
195
195
  if user_logged_in():
196
196
  new_session_id = save_interview_answers(
197
197
  metadata={"title": al_sessions_snapshot_label},
198
- additional_variables_to_filter=al_sessions_additional_variables_to_filter,
198
+ additional_variables_to_filter=showifdef('al_sessions_additional_variables_to_filter', []),
199
199
  )
200
200
  log(f"Saved interview {al_sessions_snapshot_label} with id {new_session_id}")
201
201
  else:
@@ -1773,7 +1773,7 @@ content: |
1773
1773
  --------------|-----------
1774
1774
  Date of email | ${current_datetime().format_datetime()}
1775
1775
  Pages | ${al_num_package_pages }
1776
- Preferred language | ${ user_preferred_language if user_needs_interpreter else 'English' }
1776
+ Preferred language | ${ user_preferred_language if showifdef('user_needs_interpreter') else 'English' }
1777
1777
  Mobile phone | ${showifdef('users[0].mobile_number')}
1778
1778
  Other phone | ${showifdef('users[0].phone_number')}
1779
1779
  Email | ${showifdef('users[0].email')}
@@ -0,0 +1,17 @@
1
+ ---
2
+ include:
3
+ - assembly_line.yml
4
+ ---
5
+ mandatory: True
6
+ code: |
7
+ al_sessions_additional_variables_to_filter = []
8
+ al_sessions_snapshot_label
9
+ al_sessions_save_session_snapshot
10
+ al_sessions_save_status
11
+ finish_screen
12
+ ---
13
+ id: finish screen
14
+ event: finish_screen
15
+ question: |
16
+ Done
17
+ ---
@@ -0,0 +1,10 @@
1
+ @snapshots
2
+ Feature: Test saving snapshots
3
+
4
+ Scenario: User can save an answer snapshot
5
+ Given I log in with "DA_ADMIN_EMAIL" and "DA_ADMIN_PASSWORD"
6
+ And I start the interview at "test_saving_snapshots"
7
+ And I should see the phrase "Save an answer set"
8
+ And I set the var "al_sessions_snapshot_label" to "My test snapshot"
9
+ And I tap to continue
10
+ And I should see the phrase "Your answer set was successfully saved"
@@ -31,9 +31,8 @@ from docassemble.base.util import (
31
31
  validation_error,
32
32
  word,
33
33
  )
34
- from docassemble.webapp.db_object import init_sqlalchemy
35
34
  from sqlalchemy.sql import text
36
- from docassemble.base.functions import server, safe_json, serializable_dict
35
+ from docassemble.base.functions import safe_json, serializable_dict
37
36
  from .al_document import (
38
37
  ALDocument,
39
38
  ALDocumentBundle,
@@ -47,6 +46,33 @@ import re
47
46
  import hashlib
48
47
  import struct
49
48
 
49
+ try:
50
+ from docassemble.base.hooks import write_answer_json as _write_answer_json
51
+ except ModuleNotFoundError as err:
52
+ if err.name != "docassemble.base.hooks":
53
+ raise
54
+ # docassemble < 1.10 exposes webapp hooks through the legacy server object.
55
+ from docassemble.base.functions import server as _legacy_server
56
+
57
+ def _write_answer_json(*args, **kwargs):
58
+ return _legacy_server.write_answer_json(*args, **kwargs)
59
+
60
+
61
+ try:
62
+ from docassemble.webapp.db import (
63
+ get_session as _get_session,
64
+ session_scope as _session_scope,
65
+ )
66
+ except ModuleNotFoundError as err:
67
+ if err.name != "docassemble.webapp.db":
68
+ raise
69
+ # docassemble < 1.10 uses a SQLAlchemy engine instead of session context managers.
70
+ from docassemble.webapp.db_object import init_sqlalchemy
71
+
72
+ _legacy_db = init_sqlalchemy()
73
+ _get_session = _legacy_db.connect
74
+ _session_scope = _legacy_db.begin
75
+
50
76
  try:
51
77
  import zoneinfo # type: ignore
52
78
  except ImportError:
@@ -80,8 +106,6 @@ __all__ = [
80
106
  "update_session_metadata",
81
107
  ]
82
108
 
83
- db = init_sqlalchemy()
84
-
85
109
  al_sessions_variables_to_remove: Set = {
86
110
  # Internal fields
87
111
  "_internal",
@@ -278,7 +302,7 @@ def set_interview_metadata(
278
302
  data (Dict): The metadata to add.
279
303
  metadata_key_name (str, optional): The name of the metadata key. Defaults to "metadata".
280
304
  """
281
- server.write_answer_json(
305
+ _write_answer_json(
282
306
  session_id, filename, safe_json(data), tags=metadata_key_name, persistent=True
283
307
  )
284
308
 
@@ -304,8 +328,8 @@ def get_interview_metadata(
304
328
  AND tags = :tags
305
329
  AND key = :session_id
306
330
  """)
307
- with db.connect() as con:
308
- row = con.execute(
331
+ with _get_session() as session:
332
+ row = session.execute(
309
333
  sql,
310
334
  {"filename": filename, "tags": metadata_key_name, "session_id": session_id},
311
335
  ).fetchone()
@@ -448,8 +472,8 @@ def get_saved_interview_list(
448
472
  return []
449
473
 
450
474
  sessions = []
451
- with db.connect() as con:
452
- rs = con.execute(
475
+ with _get_session() as session:
476
+ rs = session.execute(
453
477
  get_sessions_query,
454
478
  {
455
479
  "metadata": metadata_key_name,
@@ -464,8 +488,8 @@ def get_saved_interview_list(
464
488
  ), # We need to pass a value to the query, but it's treated as a flag
465
489
  },
466
490
  )
467
- for session in rs:
468
- sessions.append(dict(session._mapping))
491
+ for row in rs:
492
+ sessions.append(dict(row._mapping))
469
493
 
470
494
  return sessions
471
495
 
@@ -666,11 +690,11 @@ def find_matching_sessions(
666
690
  parameters[f"{column}_filter"] = val_tuple[0]
667
691
 
668
692
  sessions = []
669
- with db.connect() as con:
670
- rs = con.execute(get_sessions_query, parameters)
693
+ with _get_session() as session:
694
+ rs = session.execute(get_sessions_query, parameters)
671
695
 
672
- for session in rs:
673
- sessions.append(dict(session._mapping))
696
+ for row in rs:
697
+ sessions.append(dict(row._mapping))
674
698
 
675
699
  return sessions
676
700
 
@@ -720,8 +744,8 @@ def delete_interview_sessions(
720
744
 
721
745
  log(f"Deleting sessions with {user_id} {filename_to_exclude} {current_filename}")
722
746
 
723
- with db.connect() as connection:
724
- connection.execute(
747
+ with _session_scope() as session:
748
+ session.execute(
725
749
  delete_sessions_query,
726
750
  {
727
751
  "user_id": user_id,
@@ -1349,6 +1373,7 @@ def get_filtered_session_variables(
1349
1373
  all_vars = {k: v for k, v in all_vars.items() if k not in variables_to_filter}
1350
1374
 
1351
1375
  items_to_check = list(all_vars.items())
1376
+ visited = set()
1352
1377
 
1353
1378
  while items_to_check:
1354
1379
  key, value = items_to_check.pop()
@@ -1358,6 +1383,10 @@ def get_filtered_session_variables(
1358
1383
  del all_vars[key]
1359
1384
  continue
1360
1385
 
1386
+ if id(value) in visited:
1387
+ continue
1388
+ visited.add(id(value))
1389
+
1361
1390
  if isinstance(value, DAObject):
1362
1391
  # docassemble overrides both __dir__ and __getattr__ for reasons unknown
1363
1392
  # we need to use the base Python versions to get what we expect
@@ -1638,11 +1667,11 @@ def get_filenames_having_sessions(
1638
1667
  sql_all = text("SELECT DISTINCT filename FROM userdict")
1639
1668
  sql_user = text("SELECT DISTINCT filename FROM userdict WHERE user_id = :user_id")
1640
1669
 
1641
- with db.connect() as conn:
1670
+ with _get_session() as session:
1642
1671
  if user_id is None:
1643
- rows = conn.execute(sql_all).mappings().all()
1672
+ rows = session.execute(sql_all).mappings().all()
1644
1673
  else:
1645
- rows = conn.execute(sql_user, {"user_id": user_id}).mappings().all()
1674
+ rows = session.execute(sql_user, {"user_id": user_id}).mappings().all()
1646
1675
 
1647
1676
  return [row["filename"] for row in rows]
1648
1677
 
@@ -1723,49 +1752,48 @@ def update_session_metadata(
1723
1752
  h1 = to_signed_32(high_u32)
1724
1753
  h2 = to_signed_32(low_u32)
1725
1754
 
1726
- with db.connect() as con:
1727
- # Wrap in a transaction so the advisory lock holds until COMMIT
1728
- with con.begin():
1729
- # 3) Acquire the advisory lock on (h1,h2)
1730
- con.execute(
1731
- text("SELECT pg_advisory_xact_lock(:h1, :h2)"),
1732
- {"h1": h1, "h2": h2},
1733
- )
1755
+ # The advisory lock and upsert must share one transaction.
1756
+ with _session_scope() as session:
1757
+ # 3) Acquire the advisory lock on (h1,h2)
1758
+ session.execute(
1759
+ text("SELECT pg_advisory_xact_lock(:h1, :h2)"),
1760
+ {"h1": h1, "h2": h2},
1761
+ )
1734
1762
 
1735
- # 4) Try UPDATE first, using CAST() instead of ::jsonb
1736
- update_sql = text("""
1737
- UPDATE jsonstorage
1738
- SET data = jsonstorage.data || CAST(:data AS jsonb)
1739
- WHERE key = :session_id
1740
- AND filename = :filename
1741
- AND tags = :tags
1763
+ # 4) Try UPDATE first, using CAST() instead of ::jsonb
1764
+ update_sql = text("""
1765
+ UPDATE jsonstorage
1766
+ SET data = jsonstorage.data || CAST(:data AS jsonb)
1767
+ WHERE key = :session_id
1768
+ AND filename = :filename
1769
+ AND tags = :tags
1770
+ """)
1771
+ result = session.execute(
1772
+ update_sql,
1773
+ {
1774
+ "data": json_data_string,
1775
+ "session_id": session_id,
1776
+ "filename": filename,
1777
+ "tags": metadata_key_name,
1778
+ },
1779
+ )
1780
+
1781
+ # 5) If nothing was updated, INSERT
1782
+ if (result.rowcount or 0) == 0:
1783
+ insert_sql = text("""
1784
+ INSERT INTO jsonstorage (key, filename, tags, data)
1785
+ VALUES (:session_id, :filename, :tags, CAST(:data AS jsonb))
1742
1786
  """)
1743
- result = con.execute(
1744
- update_sql,
1787
+ session.execute(
1788
+ insert_sql,
1745
1789
  {
1746
- "data": json_data_string,
1747
1790
  "session_id": session_id,
1748
1791
  "filename": filename,
1749
1792
  "tags": metadata_key_name,
1793
+ "data": json_data_string,
1750
1794
  },
1751
1795
  )
1752
1796
 
1753
- # 5) If nothing was updated, INSERT
1754
- if (result.rowcount or 0) == 0:
1755
- insert_sql = text("""
1756
- INSERT INTO jsonstorage (key, filename, tags, data)
1757
- VALUES (:session_id, :filename, :tags, CAST(:data AS jsonb))
1758
- """)
1759
- con.execute(
1760
- insert_sql,
1761
- {
1762
- "session_id": session_id,
1763
- "filename": filename,
1764
- "tags": metadata_key_name,
1765
- "data": json_data_string,
1766
- },
1767
- )
1768
-
1769
1797
 
1770
1798
  def update_current_session_metadata(
1771
1799
  data: Dict[str, Any],
@@ -1,8 +1,37 @@
1
1
  # do not pre-load
2
2
 
3
+ import json
3
4
  import unittest
5
+ from html import unescape
4
6
  from docassemble.base.util import DAFile, DATemplate
5
- from .al_document import ALDocument, ALDocumentBundle, ALAddendumField
7
+ from .al_document import ALDocument, ALDocumentBundle, ALAddendumField, _javascript_href
8
+
9
+
10
+ class TestJavascriptHref(unittest.TestCase):
11
+ def test_escapes_javascript_strings_inside_html_href(self):
12
+ result = _javascript_href(
13
+ "aldocument_send_action",
14
+ "template_request.requestee_bundles['4167e36be7f04794ad30770e865afe68']",
15
+ "_ignore_al_wants_editable_bundle",
16
+ "_ignore_al_doc_email_bundle",
17
+ )
18
+
19
+ self.assertEqual(
20
+ result,
21
+ "javascript:aldocument_send_action("
22
+ "&quot;template_request.requestee_bundles[&#x27;4167e36be7f04794ad30770e865afe68&#x27;]&quot;,"
23
+ "&quot;_ignore_al_wants_editable_bundle&quot;,"
24
+ "&quot;_ignore_al_doc_email_bundle&quot;)",
25
+ )
26
+
27
+ def test_preserves_javascript_value_types(self):
28
+ result = unescape(_javascript_href("send", "None", None, ["pdf", "docx"]))
29
+ args_json = result.removeprefix("javascript:send(").removesuffix(")")
30
+
31
+ self.assertEqual(
32
+ json.loads(f"[{args_json}]"),
33
+ ["None", None, ["pdf", "docx"]],
34
+ )
6
35
 
7
36
 
8
37
  class test_dont_assume_pdf(unittest.TestCase):
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassemble.AssemblyLine
3
- Version: 4.6.3
3
+ Version: 4.7.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
  Author-email: Suffolk Legal Innovation and Technology Lab <litlab@suffolk.edu>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://courtformsonline.org
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: docassemble.ALToolbox>=0.16.0
10
+ Requires-Dist: docassemble.ALToolbox>=0.18.0
11
11
  Requires-Dist: docassemble.GitHubFeedbackForm>=0.4.1.1
12
12
  Dynamic: license-file
13
13
 
@@ -55,6 +55,7 @@ docassemble/AssemblyLine/data/questions/test_if_final.yml
55
55
  docassemble/AssemblyLine/data/questions/test_preferred_formats.yml
56
56
  docassemble/AssemblyLine/data/questions/test_preferred_name.yml
57
57
  docassemble/AssemblyLine/data/questions/test_question_library.yml
58
+ docassemble/AssemblyLine/data/questions/test_saving_snapshots.yml
58
59
  docassemble/AssemblyLine/data/questions/test_signature.yml
59
60
  docassemble/AssemblyLine/data/sources/README.md
60
61
  docassemble/AssemblyLine/data/sources/ar-words.yml
@@ -68,6 +69,7 @@ docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg
68
69
  docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf
69
70
  docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png
70
71
  docassemble/AssemblyLine/data/sources/test_question_library.feature
72
+ docassemble/AssemblyLine/data/sources/test_snapshots.feature
71
73
  docassemble/AssemblyLine/data/sources/translation_es.xlsx
72
74
  docassemble/AssemblyLine/data/sources/translation_ht.xlsx
73
75
  docassemble/AssemblyLine/data/sources/translation_pt.xlsx
@@ -1,2 +1,2 @@
1
- docassemble.ALToolbox>=0.16.0
1
+ docassemble.ALToolbox>=0.18.0
2
2
  docassemble.GitHubFeedbackForm>=0.4.1.1
@@ -6,14 +6,14 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "docassemble.AssemblyLine"
9
- version = "4.6.3"
9
+ version = "4.7.0"
10
10
  description = "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."
11
11
  readme = "README.md"
12
12
  authors = [
13
13
  { name="Suffolk Legal Innovation and Technology Lab", email="litlab@suffolk.edu" },
14
14
  ]
15
15
  dependencies = [
16
- "docassemble.ALToolbox>=0.16.0",
16
+ "docassemble.ALToolbox>=0.18.0",
17
17
  "docassemble.GitHubFeedbackForm>=0.4.1.1",
18
18
  ]
19
19
  license="MIT"
@@ -76,3 +76,5 @@ dev = [
76
76
  "types-psycopg2",
77
77
  ]
78
78
 
79
+ [tool.uv]
80
+ exclude-newer-package = {"docassemble.ALToolbox" = false}
@@ -1 +0,0 @@
1
- __version__ = '4.6.3'