docassemble.AssemblyLine 3.0.1__tar.gz → 3.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 (89) hide show
  1. {docassemble_assemblyline-3.0.1/docassemble.AssemblyLine.egg-info → docassemble_assemblyline-3.1.0}/PKG-INFO +1 -1
  2. docassemble_assemblyline-3.1.0/docassemble/AssemblyLine/__init__.py +1 -0
  3. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/al_general.py +310 -118
  4. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/interview_list.yml +16 -1
  5. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/sessions.py +90 -62
  6. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/test_al_general.py +3 -3
  7. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0/docassemble.AssemblyLine.egg-info}/PKG-INFO +1 -1
  8. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/setup.py +1 -1
  9. docassemble_assemblyline-3.0.1/docassemble/AssemblyLine/__init__.py +0 -1
  10. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/LICENSE +0 -0
  11. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/MANIFEST.in +0 -0
  12. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/README.md +0 -0
  13. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/al_courts.py +0 -0
  14. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/al_document.py +0 -0
  15. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_code.yml +0 -0
  16. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_document.yml +0 -0
  17. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_language.yml +0 -0
  18. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_package.yml +0 -0
  19. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_package_unstyled.yml +0 -0
  20. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_question_test.yml +0 -0
  21. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_reminders.yml +0 -0
  22. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions.yml +0 -0
  23. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_saved_sessions_store.yml +0 -0
  24. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_settings.yml +0 -0
  25. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_terms_of_use.yml +0 -0
  26. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/al_visual.yml +0 -0
  27. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/assembly_line.yml +0 -0
  28. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/assembly_line_unstyled.yml +0 -0
  29. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/demo_search_sessions.yml +0 -0
  30. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/feedback.yml +0 -0
  31. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/ql_baseline.yml +0 -0
  32. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/ql_catchall.yml +0 -0
  33. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/reserved_words.yml +0 -0
  34. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_al_table_document.yml +0 -0
  35. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_aladdendum.yml +0 -0
  36. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_alcourts.yml +0 -0
  37. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_aldocument.yml +0 -0
  38. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_alexhibit.yml +0 -0
  39. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_alexhibit_maxsize.yml +0 -0
  40. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_bundle_parity.yml +0 -0
  41. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_familiar_disambiguation.yml +0 -0
  42. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/questions/test_question_library.yml +0 -0
  43. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/README.md +0 -0
  44. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/es-words.yml +0 -0
  45. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/languages.yml +0 -0
  46. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_aldocument.feature +0 -0
  47. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_docx_1.docx +0 -0
  48. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_jpg_1.jpg +0 -0
  49. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_pdf_1.pdf +0 -0
  50. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_alexhibit_png_1.png +0 -0
  51. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/test_question_library.feature +0 -0
  52. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/translation_es.xlsx +0 -0
  53. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/translation_ht.xlsx +0 -0
  54. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/sources/translation_pt.xlsx +0 -0
  55. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/README.md +0 -0
  56. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/al_audio.css +0 -0
  57. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/al_audio.js +0 -0
  58. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/aldocument.css +0 -0
  59. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/aldocument.js +0 -0
  60. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/interview_list.css +0 -0
  61. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/limit_upload_size.js +0 -0
  62. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/lt1-pullout-10-getting-organized.pdf +0 -0
  63. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/ma_logo.png +0 -0
  64. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/placeholder.png +0 -0
  65. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/seal.jpg +0 -0
  66. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/static/styles.css +0 -0
  67. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/README.md +0 -0
  68. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/al_basic_addendum.docx +0 -0
  69. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/exhibit_cover_sheet.docx +0 -0
  70. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/exhibit_table_of_contents.docx +0 -0
  71. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/general_cover_sheet.docx +0 -0
  72. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/sample_word_template.docx +0 -0
  73. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/test_aldocument_docx_1.docx +0 -0
  74. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/data/templates/test_aldocument_pdf_1.pdf +0 -0
  75. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/language.py +0 -0
  76. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/project_maintenance.py +0 -0
  77. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/py.typed +0 -0
  78. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/requirements.txt +0 -0
  79. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/test_al_document.py +0 -0
  80. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/AssemblyLine/test_language.py +0 -0
  81. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble/__init__.py +0 -0
  82. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/SOURCES.txt +0 -0
  83. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/dependency_links.txt +0 -0
  84. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/namespace_packages.txt +0 -0
  85. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/not-zip-safe +0 -0
  86. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/requires.txt +0 -0
  87. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/docassemble.AssemblyLine.egg-info/top_level.txt +0 -0
  88. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/pyproject.toml +0 -0
  89. {docassemble_assemblyline-3.0.1 → docassemble_assemblyline-3.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docassemble.AssemblyLine
3
- Version: 3.0.1
3
+ Version: 3.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
@@ -0,0 +1 @@
1
+ __version__ = '3.1.0'
@@ -804,6 +804,46 @@ class ALPeopleList(DAList):
804
804
  and_string=and_string,
805
805
  )
806
806
 
807
+ def pronoun_reflexive(self, **kwargs) -> str:
808
+ """Returns the appropriate reflexive pronoun for the list of people, depending
809
+ on the `person` keyword argument and the number of items in the list.
810
+
811
+ If the list is singular, return the reflexive pronoun for the first item in the list.
812
+ If it is plural, return the appropriate plural reflexive pronoun (e.g., "themselves")
813
+
814
+ Args:
815
+ **kwargs: Additional keyword arguments that are defined [upstream](https://docassemble.org/docs/objects.html#language%20methods).
816
+ - person (Optional[[Union[str,int]]): Whether to use a first, second, or third person pronoun. Can be one of 1/"1p", 2/"2p", or 3/"3p" (default is 3). See [upstream](https://docassemble.org/docs/objects.html#language%20methods) documentation for more information.
817
+ - default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
818
+
819
+ Returns:
820
+ str: The reflexive pronoun for the list.
821
+ """
822
+ person = str(kwargs.get("person", self.get_point_of_view()))
823
+
824
+ if self.number_gathered() > 1:
825
+ if person in ("1", "1p", "2", "2p"):
826
+ if person in ("1", "1p"):
827
+ output = word("ourselves")
828
+ elif person in ("2", "2p"):
829
+ output = word("yourselves")
830
+ else:
831
+ output = word("themselves")
832
+
833
+ elif self.number_gathered() == 1:
834
+ if isinstance(self[0], ALIndividual) and hasattr(
835
+ self[0], "pronoun_reflexive"
836
+ ):
837
+ output = self[0].pronoun_reflexive(**kwargs)
838
+ else:
839
+ output = word("itself")
840
+ else:
841
+ output = word("themselves")
842
+
843
+ if kwargs.get("capitalize"):
844
+ return output.capitalize()
845
+ return output
846
+
807
847
 
808
848
  class ALIndividual(Individual):
809
849
  """Used to represent an Individual on the assembly line project.
@@ -1562,18 +1602,18 @@ class ALIndividual(Individual):
1562
1602
  )
1563
1603
 
1564
1604
  def pronoun(self, **kwargs) -> str:
1565
- """Returns an objective pronoun as appropriate, based on attributes.
1605
+ """Returns an objective pronoun as appropriate, based on the user's `pronouns` attribute or `gender` attribute.
1566
1606
 
1567
- The pronoun could be "you," "her," "him," "it," or "them". It depends
1568
- on the `gender` and `person_type` attributes and whether the individual
1569
- is the current user.
1607
+ The pronoun could be "I", "you," "her," "him," "it," or "them", or a user-provided pronoun.
1608
+ If the user has selected multiple pronouns, each will appear, separated by a "/".
1570
1609
 
1571
- If the user selected specific pronouns, they take priority over
1572
- gender (only if they chose a pronoun from the list)
1610
+ This method will not trigger the definition of `gender` or `pronouns`, but it will use them if they are defined,
1611
+ with `pronouns` taking precedence. As a default, it will either use the value of `default` or the individual's full name.
1573
1612
 
1574
1613
  Args:
1575
- **kwargs: Additional keyword arguments.
1576
-
1614
+ **kwargs: Additional keyword arguments that are defined [upstream](https://docassemble.org/docs/objects.html#language%20methods).
1615
+ - person (Optional[[Union[str,int]]): Whether to use a first, second, or third person pronoun. Can be one of 1/"1p", 2/"2p", or 3/"3p" (default is 3). See [upstream](https://docassemble.org/docs/objects.html#language%20methods) documentation for more information.
1616
+ - default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
1577
1617
  Returns:
1578
1618
  str: The appropriate pronoun.
1579
1619
  """
@@ -1583,49 +1623,64 @@ class ALIndividual(Individual):
1583
1623
  # Use the parent version of pronoun
1584
1624
  return super().pronoun(**kwargs)
1585
1625
 
1586
- if hasattr(self, "pronouns") and isinstance(self.pronouns, str):
1587
- pronouns = DADict(elements={self.pronouns.lower(): True})
1626
+ if "default" in kwargs:
1627
+ default = kwargs.pop("default")
1588
1628
  else:
1589
- pronouns = self.pronouns
1629
+ default = self.name_full()
1630
+
1631
+ if hasattr(self, "pronouns") and self.pronouns:
1632
+ if isinstance(self.pronouns, str):
1633
+ pronouns = DADict(elements={self.pronouns.lower(): True})
1634
+ else:
1635
+ pronouns = self.pronouns
1590
1636
 
1591
1637
  if self == this_thread.global_vars.user:
1592
1638
  output = word("you", **kwargs)
1593
- elif (
1594
- hasattr(self, "pronouns")
1595
- and isinstance(pronouns, DADict)
1596
- and len(pronouns.true_values()) == 1
1597
- and (
1598
- (
1599
- pronouns.true_values()[0]
1600
- in ["she/her/hers", "he/him/his", "they/them/theirs", "ze/zir/zirs"]
1601
- )
1602
- or (
1603
- pronouns.get("self-described")
1604
- and has_parsable_pronouns(self.pronouns_self_described)
1605
- )
1606
- )
1607
- ):
1608
- if pronouns.get("she/her/hers"):
1609
- output = word("her", **kwargs)
1610
- elif pronouns.get("he/him/his"):
1611
- output = word("him", **kwargs)
1612
- elif pronouns.get("they/them/theirs"):
1613
- output = word("them", **kwargs)
1614
- elif pronouns.get("ze/zir/zirs"):
1615
- output = word("zir", **kwargs)
1616
- elif pronouns.get("self-described"):
1617
- output = parse_custom_pronouns(self.pronouns_self_described)["o"]
1639
+ elif hasattr(self, "pronouns") and self.pronouns:
1640
+ pronouns_to_use = []
1641
+ if isinstance(pronouns, DADict):
1642
+ for pronoun in pronouns.true_values():
1643
+ if pronoun in [
1644
+ "she/her/hers",
1645
+ "he/him/his",
1646
+ "they/them/theirs",
1647
+ "ze/zir/zirs",
1648
+ ]:
1649
+ if pronoun == "she/her/hers":
1650
+ pronouns_to_use.append(word("her", **kwargs))
1651
+ elif pronoun == "he/him/his":
1652
+ pronouns_to_use.append(word("him", **kwargs))
1653
+ elif pronoun == "they/them/theirs":
1654
+ pronouns_to_use.append(word("them", **kwargs))
1655
+ elif pronoun == "ze/zir/zirs":
1656
+ pronouns_to_use.append(word("zir", **kwargs))
1657
+ elif pronoun == "self-described" and has_parsable_pronouns(
1658
+ self.pronouns_self_described
1659
+ ):
1660
+ pronouns_to_use.append(
1661
+ parse_custom_pronouns(self.pronouns_self_described)["o"]
1662
+ )
1663
+ elif has_parsable_pronouns(pronoun):
1664
+ pronouns_to_use.append(parse_custom_pronouns(pronoun)["o"])
1665
+ if len(pronouns_to_use) > 0:
1666
+ output = "/".join(pronouns_to_use)
1667
+ else:
1668
+ output = default
1618
1669
  elif hasattr(self, "person_type") and self.person_type in [
1619
1670
  "business",
1620
1671
  "organization",
1621
1672
  ]:
1622
1673
  output = word("it", **kwargs)
1623
- elif self.gender.lower() == "female":
1624
- output = word("her", **kwargs)
1625
- elif self.gender.lower() == "male":
1626
- output = word("him", **kwargs)
1674
+ elif hasattr(self, "gender"):
1675
+ if self.gender.lower() == "female":
1676
+ output = word("her", **kwargs)
1677
+ elif self.gender.lower() == "male":
1678
+ output = word("him", **kwargs)
1679
+ else:
1680
+ output = word("them", **kwargs)
1627
1681
  else:
1628
- output = word("them", **kwargs)
1682
+ output = default
1683
+
1629
1684
  if "capitalize" in kwargs and kwargs["capitalize"]:
1630
1685
  return capitalize(output)
1631
1686
  return output
@@ -1642,18 +1697,24 @@ class ALIndividual(Individual):
1642
1697
  return self.pronoun(**kwargs)
1643
1698
 
1644
1699
  def pronoun_possessive(self, target, **kwargs) -> str:
1645
- """Returns a possessive pronoun and a target word, based on attributes.
1700
+ """
1701
+ Returns a possessive pronoun and a target word, based on attributes.
1702
+
1703
+ This method will not trigger the definition of `gender` or `pronouns`, but it will use them if they are defined,
1704
+ with `pronouns` taking precedence. As a default, it will either use the value of `default` or the individual's full name.
1646
1705
 
1647
1706
  Given a target word, the function returns "{pronoun} {target}". The pronoun could be
1648
- "her," "his," "its," or "their". It depends on the `gender` and `person_type` attributes
1707
+ "my", "her," "his," "its," or "their". It depends on the `gender` and `person_type` attributes
1649
1708
  and whether the individual is the current user.
1650
1709
 
1651
1710
  Args:
1652
1711
  target (str): The target word to follow the pronoun.
1653
- **kwargs: Additional keyword arguments.
1712
+ **kwargs: Additional keyword arguments that can be passed to modify the behavior. These might include:
1713
+ - `default` (Optional[str]): The default word to use if the pronoun is not defined, e.g., "the agent". If not defined, the default term is the user's name.
1714
+ - `person` (Optional[Union[str, int]]): Whether to use a first, second, or third person pronoun. Can be one of 1/"1p", 2/"2p", or 3/"3p" (default is 3). See [upstream documentation](https://docassemble.org/docs/objects.html#language%20methods) for more information.
1654
1715
 
1655
1716
  Returns:
1656
- str: The appropriate possessive phrase.
1717
+ str: The appropriate possessive phrase, e.g., "her book", "their document".
1657
1718
  """
1658
1719
  person = str(kwargs.get("person", self.get_point_of_view()))
1659
1720
 
@@ -1666,50 +1727,64 @@ class ALIndividual(Individual):
1666
1727
  else:
1667
1728
  pronouns = self.pronouns
1668
1729
 
1730
+ if "default" in kwargs:
1731
+ default = kwargs.pop("default")
1732
+ else:
1733
+ default = self.name_full()
1734
+
1669
1735
  if self == this_thread.global_vars.user and (
1670
1736
  "thirdperson" not in kwargs or not kwargs["thirdperson"]
1671
1737
  ):
1672
1738
  output = your(target, **kwargs)
1673
- elif (
1674
- hasattr(self, "pronouns")
1675
- and isinstance(pronouns, DADict)
1676
- and len(pronouns.true_values()) == 1
1677
- and (
1678
- (
1679
- pronouns.true_values()[0]
1680
- in ["she/her/hers", "he/him/his", "they/them/theirs", "ze/zir/zirs"]
1681
- )
1682
- or (
1683
- pronouns.get("self-described")
1684
- and has_parsable_pronouns(self.pronouns_self_described)
1685
- )
1686
- )
1687
- ):
1688
- if pronouns.get("she/her/hers"):
1689
- output = her(target, **kwargs)
1690
- elif pronouns.get("he/him/his"):
1691
- output = his(target, **kwargs)
1692
- elif pronouns.get("they/them/theirs"):
1693
- output = their(target, **kwargs)
1694
- elif pronouns.get("ze/zir/zirs"):
1695
- output = word("zir", **kwargs) + " " + target
1696
- elif pronouns.get("self-described"):
1697
- output = (
1698
- parse_custom_pronouns(self.pronouns_self_described)["p"]
1699
- + " "
1700
- + target
1701
- )
1739
+ elif hasattr(self, "pronouns") and self.pronouns:
1740
+ pronouns_to_use = []
1741
+ if isinstance(pronouns, DADict):
1742
+ for pronoun in pronouns.true_values():
1743
+ if pronoun in [
1744
+ "she/her/hers",
1745
+ "he/him/his",
1746
+ "they/them/theirs",
1747
+ "ze/zir/zirs",
1748
+ ]:
1749
+ if pronoun == "she/her/hers":
1750
+ pronouns_to_use.append(her(target, **kwargs))
1751
+ elif pronoun == "he/him/his":
1752
+ pronouns_to_use.append(his(target, **kwargs))
1753
+ elif pronoun == "they/them/theirs":
1754
+ pronouns_to_use.append(their(target, **kwargs))
1755
+ elif pronoun == "ze/zir/zirs":
1756
+ pronouns_to_use.append(word("zir", **kwargs) + " " + target)
1757
+ elif pronoun == "self-described" and has_parsable_pronouns(
1758
+ self.pronouns_self_described
1759
+ ):
1760
+ pronouns_to_use.append(
1761
+ parse_custom_pronouns(self.pronouns_self_described)["p"]
1762
+ + " "
1763
+ + target
1764
+ )
1765
+ elif has_parsable_pronouns(pronoun):
1766
+ pronouns_to_use.append(
1767
+ parse_custom_pronouns(pronoun)["p"] + " " + target
1768
+ )
1769
+ if len(pronouns_to_use) > 0:
1770
+ output = "/".join(pronouns_to_use)
1771
+ else:
1772
+ output = default
1702
1773
  elif hasattr(self, "person_type") and self.person_type in [
1703
1774
  "business",
1704
1775
  "organization",
1705
1776
  ]:
1706
1777
  output = its(target, **kwargs)
1707
- elif self.gender.lower() == "female":
1708
- output = her(target, **kwargs)
1709
- elif self.gender.lower() == "male":
1710
- output = his(target, **kwargs)
1778
+ elif hasattr(self, "gender"):
1779
+ if self.gender.lower() == "female":
1780
+ output = her(target, **kwargs)
1781
+ elif self.gender.lower() == "male":
1782
+ output = his(target, **kwargs)
1783
+ else:
1784
+ output = their(target, **kwargs)
1711
1785
  else:
1712
- output = their(target, **kwargs)
1786
+ output = default
1787
+
1713
1788
  if "capitalize" in kwargs and kwargs["capitalize"]:
1714
1789
  return capitalize(output)
1715
1790
  return output
@@ -1717,13 +1792,14 @@ class ALIndividual(Individual):
1717
1792
  def pronoun_subjective(self, **kwargs) -> str:
1718
1793
  """Returns a subjective pronoun, based on attributes.
1719
1794
 
1720
- The pronoun could be "you," "she," "he," "it," or "they". It depends
1795
+ The pronoun could be "you," "we", "she," "he," "it," or "they". It depends
1721
1796
  on the `gender` and `person_type` attributes and whether the individual
1722
1797
  is the current user.
1723
1798
 
1724
1799
  Args:
1725
- **kwargs: Additional keyword arguments.
1726
-
1800
+ **kwargs: Additional keyword arguments that are defined [upstream](https://docassemble.org/docs/objects.html#language%20methods).
1801
+ - person (Optional[[Union[str,int]]): Whether to use a first, second, or third person pronoun. Can be one of 1/"1p", 2/"2p", or 3/"3p" (default is 3). See [upstream](https://docassemble.org/docs/objects.html#language%20methods) documentation for more information.
1802
+ - default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
1727
1803
  Returns:
1728
1804
  str: The appropriate subjective pronoun.
1729
1805
  """
@@ -1732,53 +1808,169 @@ class ALIndividual(Individual):
1732
1808
  if person in ("1", "1p", "2", "2p"):
1733
1809
  # Use the parent version of pronoun
1734
1810
  return super().pronoun_subjective(**kwargs)
1735
-
1736
- if hasattr(self, "pronouns") and isinstance(self.pronouns, str):
1737
- pronouns = DADict(elements={self.pronouns.lower(): True})
1811
+ if "default" in kwargs:
1812
+ default = kwargs.pop("default")
1738
1813
  else:
1739
- pronouns = self.pronouns
1814
+ default = self.name_full()
1740
1815
 
1741
- if self == this_thread.global_vars.user and (
1742
- "thirdperson" not in kwargs or not kwargs["thirdperson"]
1743
- ):
1816
+ if hasattr(self, "pronouns") and self.pronouns:
1817
+ if isinstance(self.pronouns, str):
1818
+ pronouns = DADict(elements={self.pronouns.lower(): True})
1819
+ else:
1820
+ pronouns = self.pronouns
1821
+
1822
+ if self == this_thread.global_vars.user:
1744
1823
  output = word("you", **kwargs)
1745
- elif (
1746
- hasattr(self, "pronouns")
1747
- and isinstance(pronouns, DADict)
1748
- and len(pronouns.true_values()) == 1
1749
- and (
1750
- (
1751
- pronouns.true_values()[0]
1752
- in ["she/her/hers", "he/him/his", "they/them/theirs", "ze/zir/zirs"]
1753
- )
1754
- or (
1755
- pronouns.get("self-described")
1756
- and has_parsable_pronouns(self.pronouns_self_described)
1757
- )
1758
- )
1759
- ):
1760
- if pronouns.get("she/her/hers"):
1824
+ elif hasattr(self, "pronouns") and self.pronouns:
1825
+ pronouns_to_use = []
1826
+ if isinstance(pronouns, DADict):
1827
+ for pronoun in pronouns.true_values():
1828
+ if pronoun in [
1829
+ "she/her/hers",
1830
+ "he/him/his",
1831
+ "they/them/theirs",
1832
+ "ze/zir/zirs",
1833
+ ]:
1834
+ if pronoun == "she/her/hers":
1835
+ pronouns_to_use.append(word("she", **kwargs))
1836
+ elif pronoun == "he/him/his":
1837
+ pronouns_to_use.append(word("he", **kwargs))
1838
+ elif pronoun == "they/them/theirs":
1839
+ pronouns_to_use.append(word("they", **kwargs))
1840
+ elif pronoun == "ze/zir/zirs":
1841
+ pronouns_to_use.append(word("ze", **kwargs))
1842
+ elif pronoun == "self-described" and has_parsable_pronouns(
1843
+ self.pronouns_self_described
1844
+ ):
1845
+ pronouns_to_use.append(
1846
+ parse_custom_pronouns(self.pronouns_self_described)["s"]
1847
+ )
1848
+ elif has_parsable_pronouns(pronoun):
1849
+ pronouns_to_use.append(parse_custom_pronouns(pronoun)["s"])
1850
+ if len(pronouns_to_use) > 0:
1851
+ output = "/".join(pronouns_to_use)
1852
+ else:
1853
+ output = default
1854
+ elif hasattr(self, "person_type") and self.person_type in [
1855
+ "business",
1856
+ "organization",
1857
+ ]:
1858
+ output = word("it", **kwargs)
1859
+ elif hasattr(self, "gender"):
1860
+ if self.gender.lower() == "female":
1761
1861
  output = word("she", **kwargs)
1762
- elif pronouns.get("he/him/his"):
1862
+ elif self.gender.lower() == "male":
1763
1863
  output = word("he", **kwargs)
1764
- elif pronouns.get("they/them/theirs"):
1864
+ else:
1765
1865
  output = word("they", **kwargs)
1766
- elif pronouns.get("ze/zir/zirs"):
1767
- output = word("ze", **kwargs)
1768
- elif pronouns.get("self-described"):
1769
- output = parse_custom_pronouns(self.pronouns_self_described)["s"]
1866
+ else:
1867
+ output = default
1868
+
1869
+ if "capitalize" in kwargs and kwargs["capitalize"]:
1870
+ return capitalize(output)
1871
+ return output
1872
+
1873
+ def pronoun_reflexive(self, **kwargs) -> str:
1874
+ """Returns the appropriate reflexive pronoun ("herself", "themself", "myself"), based on the user's pronouns or gender and whether we are asked
1875
+ to return a 1st, 2nd, or 3rd person pronoun.
1876
+
1877
+ Note that if the person has pronouns of they/them/theirs or a nonbinary gender, we return "themself" as the singular non-gendered
1878
+ reflexive pronoun and not "themselves". This has growing acceptance although some consider it nonstandard.
1879
+ See: https://www.merriam-webster.com/wordplay/themself
1880
+
1881
+ Args:
1882
+ **kwargs: Additional keyword arguments that are defined [upstream](https://docassemble.org/docs/objects.html#language%20methods).
1883
+ - person (Optional[[Union[str,int]]): Whether to use a first, second, or third person pronoun. Can be one of 1/"1p", 2/"2p", or 3/"3p" (default is 3). See [upstream](https://docassemble.org/docs/objects.html#language%20methods) documentation for more information.
1884
+ - default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
1885
+
1886
+ Returns:
1887
+ str: The appropriate reflexive pronoun.
1888
+ """
1889
+ person = str(kwargs.get("person", self.get_point_of_view()))
1890
+
1891
+ if person in ("1", "1p", "2", "2p"):
1892
+ if person == "1":
1893
+ return word("myself")
1894
+ if person == "1p":
1895
+ return word("ourselves")
1896
+ if person == "2":
1897
+ return word("yourself")
1898
+ if person == "2p":
1899
+ return word("yourselves")
1900
+
1901
+ if self == this_thread.global_vars.user:
1902
+ output = word("yourself", **kwargs)
1903
+
1904
+ if "default" in kwargs:
1905
+ default = kwargs.pop("default")
1906
+ else:
1907
+ default = None
1908
+
1909
+ if hasattr(self, "pronouns") and self.pronouns:
1910
+ if isinstance(self.pronouns, str):
1911
+ pronouns = DADict(elements={self.pronouns.lower(): True})
1912
+ else:
1913
+ pronouns = self.pronouns
1914
+ else:
1915
+ pronouns = None
1916
+
1917
+ if self == this_thread.global_vars.user:
1918
+ output = word("yourself", **kwargs)
1919
+ elif hasattr(self, "pronouns") and self.pronouns:
1920
+ pronouns_to_use = []
1921
+
1922
+ if isinstance(pronouns, DADict):
1923
+ for pronoun in pronouns.true_values():
1924
+ if pronoun in [
1925
+ "she/her/hers",
1926
+ "he/him/his",
1927
+ "they/them/theirs",
1928
+ "ze/zir/zirs",
1929
+ ]:
1930
+ if pronoun == "she/her/hers":
1931
+ pronouns_to_use.append(word("herself", **kwargs))
1932
+ elif pronoun == "he/him/his":
1933
+ pronouns_to_use.append(word("himself", **kwargs))
1934
+ elif pronoun == "they/them/theirs":
1935
+ pronouns_to_use.append(word("themself", **kwargs))
1936
+ elif pronoun == "ze/zir/zirs":
1937
+ pronouns_to_use.append(word("zirself", **kwargs))
1938
+ elif pronoun == "self-described" and has_parsable_pronouns(
1939
+ self.pronouns_self_described
1940
+ ):
1941
+ pronouns_to_use.append(
1942
+ parse_custom_pronouns(self.pronouns_self_described)["o"]
1943
+ + word("self")
1944
+ )
1945
+ elif has_parsable_pronouns(pronoun):
1946
+ pronouns_to_use.append(
1947
+ parse_custom_pronouns(pronoun)["o"] + word("self")
1948
+ )
1949
+ if len(pronouns_to_use) > 0:
1950
+ output = "/".join(pronouns_to_use)
1951
+ else:
1952
+ output = default
1770
1953
  elif hasattr(self, "person_type") and self.person_type in [
1771
1954
  "business",
1772
1955
  "organization",
1773
1956
  ]:
1774
- output = word("it", **kwargs)
1775
- elif self.gender.lower() == "female":
1776
- output = word("she", **kwargs)
1777
- elif self.gender.lower() == "male":
1778
- output = word("he", **kwargs)
1957
+ output = word("itself", **kwargs)
1958
+ elif hasattr(self, "gender"):
1959
+ if self.gender.lower() == "female":
1960
+ output = word("herself", **kwargs)
1961
+ elif self.gender.lower() == "male":
1962
+ output = word("himself", **kwargs)
1963
+ else:
1964
+ output = word("themself", **kwargs)
1779
1965
  else:
1780
- output = word("they", **kwargs)
1781
- if "capitalize" in kwargs and kwargs["capitalize"]:
1966
+ if default:
1967
+ output = default
1968
+ else:
1969
+ output = word(
1970
+ "themself"
1971
+ ) # reflexive pronoun shouldn't be the person's name
1972
+
1973
+ if kwargs.get("capitalize"):
1782
1974
  return capitalize(output)
1783
1975
  return output
1784
1976
 
@@ -425,4 +425,19 @@ code: |
425
425
  code: |
426
426
  LOGO_TITLE_ROW_2 = config_with_language_fallback("logo title row 2")
427
427
  ---
428
- code: al_sessions_to_exclude_from_interview_list = get_config("assembly line",{}).get("interview list",{}).get("exclude from interview list", get_config("assembly line",{}).get("exclude from interview list"))
428
+ code: |
429
+ al_sessions_to_exclude_from_interview_list = (
430
+ get_config("assembly line", {})
431
+ .get("interview list", {})
432
+ .get(
433
+ "exclude from interview list",
434
+ get_config("assembly line", {}).get(
435
+ "exclude from interview list",
436
+ [
437
+ "docassemble.ALDashboard",
438
+ "docassemble.AssemblyLine:data/questions/al_saved_sessions_store.yml",
439
+ "docassemble.AssemblyLine:data/questions/interview_list.yml",
440
+ ],
441
+ ),
442
+ )
443
+ )
@@ -334,54 +334,12 @@ def get_saved_interview_list(
334
334
  offset (int, optional): The offset to start returning results from. Defaults to 0.
335
335
  filename_to_exclude (str, optional): The filename to exclude from the results. Defaults to "".
336
336
  exclude_current_filename (bool, optional): Whether to exclude the current filename from the results. Defaults to True.
337
- exclude_filenames (Optional[List[str]], optional): A list of filenames to exclude from the results. Defaults to None.
337
+ exclude_filenames (Optional[List[str]], optional): List of filenames to exclude. Defaults to None. If the `filename` does not contain a `:` it will be treated as a prefix, allowing you to filter out whole packages (e.g., any path starting with docassemble.ALDashboard or docassemble.playground)
338
338
  exclude_newly_started_sessions (bool, optional): Whether to exclude sessions that are still on "step 1". Defaults to False.
339
339
 
340
340
  Returns:
341
341
  List[Dict[str, Any]]: A list of saved sessions for the specified filename.
342
342
  """
343
- # We use an `offset` instead of a cursor because it is simpler and clearer
344
- # while it appears to be performant enough for real-world usage.
345
- # Up to ~ 1,000 sessions performs well and is higher than expected for an end-user
346
- get_sessions_query = text(
347
- """
348
- SELECT userdict.indexno
349
- ,userdict.filename as filename
350
- ,num_keys
351
- ,userdictkeys.user_id as user_id
352
- ,userdict.modtime as modtime
353
- ,userdict.key as key
354
- ,jsonstorage.data->'auto_title' as auto_title
355
- ,jsonstorage.data->'title' as title
356
- ,jsonstorage.data->'description' as description
357
- ,jsonstorage.data->'steps' as steps
358
- ,jsonstorage.data->'progress' as progress
359
- ,jsonstorage.data->'original_interview_filename' as original_interview_filename
360
- ,jsonstorage.data->'answer_count' as answer_count
361
- ,jsonstorage.data as data
362
- FROM userdict
363
- NATURAL JOIN
364
- (
365
- SELECT key
366
- ,MAX(modtime) AS modtime
367
- ,COUNT(key) AS num_keys
368
- FROM userdict
369
- GROUP BY key
370
- ) mostrecent
371
- LEFT JOIN userdictkeys
372
- ON userdictkeys.key = userdict.key
373
- LEFT JOIN jsonstorage
374
- ON userdict.key = jsonstorage.key AND (jsonstorage.tags = :metadata)
375
- WHERE (userdictkeys.user_id = :user_id or :user_id is null)
376
- AND (userdict.filename = :filename OR :filename is null)
377
- AND (userdict.filename NOT IN :filenames_to_exclude)
378
- AND (NOT :exclude_newly_started_sessions OR num_keys > 1)
379
- ORDER BY modtime desc
380
- LIMIT :limit
381
- OFFSET :offset;
382
- """
383
- )
384
-
385
343
  if offset < 0:
386
344
  offset = 0
387
345
 
@@ -393,10 +351,69 @@ def get_saved_interview_list(
393
351
  current_filename = ""
394
352
  if not filename_to_exclude:
395
353
  filename_to_exclude = ""
396
- filenames_to_exclude = []
354
+ filenames_to_exclude: List[str] = []
355
+ packages_to_exclude: List[str] = []
397
356
  if exclude_filenames:
398
- filenames_to_exclude.extend(exclude_filenames)
357
+ for f in filenames_to_exclude:
358
+ if f and (":" not in f):
359
+ packages_to_exclude.append(f)
360
+ else:
361
+ filenames_to_exclude.append(f)
399
362
  filenames_to_exclude.extend([current_filename, filename_to_exclude])
363
+
364
+ query_draft = """
365
+ SELECT userdict.indexno
366
+ ,userdict.filename as filename
367
+ ,num_keys
368
+ ,userdictkeys.user_id as user_id
369
+ ,userdict.modtime as modtime
370
+ ,userdict.key as key
371
+ ,jsonstorage.data->'auto_title' as auto_title
372
+ ,jsonstorage.data->'title' as title
373
+ ,jsonstorage.data->'description' as description
374
+ ,jsonstorage.data->'steps' as steps
375
+ ,jsonstorage.data->'progress' as progress
376
+ ,jsonstorage.data->'original_interview_filename' as original_interview_filename
377
+ ,jsonstorage.data->'answer_count' as answer_count
378
+ ,jsonstorage.data as data
379
+ FROM userdict
380
+ NATURAL JOIN
381
+ (
382
+ SELECT key
383
+ ,MAX(modtime) AS modtime
384
+ ,COUNT(key) AS num_keys
385
+ FROM userdict
386
+ GROUP BY key
387
+ ) mostrecent
388
+ LEFT JOIN userdictkeys
389
+ ON userdictkeys.key = userdict.key
390
+ LEFT JOIN jsonstorage
391
+ ON userdict.key = jsonstorage.key AND (jsonstorage.tags = :metadata)
392
+ WHERE (userdictkeys.user_id = :user_id or :user_id is null)
393
+ AND (userdict.filename = :filename OR :filename is null)
394
+ AND (userdict.filename NOT IN :filenames_to_exclude)
395
+ AND (NOT :exclude_newly_started_sessions OR num_keys > 1)
396
+ """
397
+ if packages_to_exclude:
398
+ query_draft += (
399
+ """
400
+ AND (:packages_to_exclude IS NULL OR NOT (
401
+ """
402
+ + " OR ".join(
403
+ [f"userdict.filename LIKE '{name}%'" for name in packages_to_exclude]
404
+ )
405
+ + """
406
+ ))
407
+ """
408
+ )
409
+ query_draft += """
410
+ ORDER BY modtime desc
411
+ LIMIT :limit
412
+ OFFSET :offset;
413
+ """
414
+
415
+ get_sessions_query = text(query_draft)
416
+
400
417
  if user_id is None:
401
418
  if user_logged_in():
402
419
  user_id = user_info().id
@@ -427,6 +444,9 @@ def get_saved_interview_list(
427
444
  "offset": offset,
428
445
  "filenames_to_exclude": tuple(filenames_to_exclude),
429
446
  "exclude_newly_started_sessions": exclude_newly_started_sessions,
447
+ "packages_to_exclude": (
448
+ None if not packages_to_exclude else "present"
449
+ ), # We need to pass a value to the query, but it's treated as a flag
430
450
  },
431
451
  )
432
452
  sessions = []
@@ -486,10 +506,13 @@ def find_matching_sessions(
486
506
  )
487
507
 
488
508
  # Construct the dynamic part of the SQL query for metadata column selection and keyword search
489
- metadata_search_conditions = " OR ".join(
490
- f"COALESCE(jsonstorage.data->>{repr(column)}, '') ILIKE '%' || :keyword || '%'"
491
- for column in metadata_column_names
492
- )
509
+ if keyword:
510
+ metadata_search_conditions = " OR ".join(
511
+ f"COALESCE(jsonstorage.data->>{repr(column)}, '') ILIKE '%' || :keyword || '%'"
512
+ for column in metadata_column_names
513
+ )
514
+ else:
515
+ metadata_search_conditions = "TRUE"
493
516
 
494
517
  # we retrieve the default metadata columns even if we don't search them
495
518
  metadata_column_names = set(metadata_column_names).union(
@@ -565,19 +588,23 @@ def find_matching_sessions(
565
588
  log("Asked to get interview list for user that is not logged in")
566
589
  return []
567
590
 
591
+ parameters = {
592
+ "metadata": metadata_key_name,
593
+ "keyword": keyword,
594
+ "user_id": user_id,
595
+ "limit": limit,
596
+ "offset": offset,
597
+ "filenames_to_exclude": tuple(filenames_to_exclude),
598
+ "exclude_newly_started_sessions": exclude_newly_started_sessions,
599
+ }
600
+
601
+ # Add filename parameters
602
+ if filenames:
603
+ for i, filename in enumerate(filenames):
604
+ parameters[f"filename{i}"] = filename
605
+
568
606
  with db.connect() as con:
569
- rs = con.execute(
570
- get_sessions_query,
571
- {
572
- "metadata": metadata_key_name,
573
- "keyword": keyword,
574
- "user_id": user_id,
575
- "limit": limit,
576
- "offset": offset,
577
- "filenames_to_exclude": tuple(filenames_to_exclude),
578
- "exclude_newly_started_sessions": exclude_newly_started_sessions,
579
- },
580
- )
607
+ rs = con.execute(get_sessions_query, parameters)
581
608
 
582
609
  sessions = []
583
610
  for session in rs:
@@ -921,7 +948,7 @@ def session_list_html(
921
948
  metadata_key_name (str, optional): Name of the metadata key. Defaults to "metadata".
922
949
  filename_to_exclude (str, optional): Name of the file to exclude. Defaults to `al_session_store_default_filename`.
923
950
  exclude_current_filename (bool, optional): If True, excludes the current filename. Defaults to True.
924
- exclude_filenames (Optional[List[str]], optional): List of filenames to exclude. Defaults to None.
951
+ exclude_filenames (Optional[List[str]], optional): List of filenames to exclude. Defaults to None. If the `filename` does not contain a `:` it will be treated as a prefix, allowing you to filter out whole packages (e.g., any path starting with docassemble.ALDashboard or docassemble.playground)
925
952
  exclude_newly_started_sessions (bool, optional): If True, excludes newly started sessions. Defaults to False.
926
953
  name_label (str, optional): Label for the session name/title. Defaults to translated word "Title".
927
954
  date_label (str, optional): Label for the date column. Defaults to translated word "Date modified".
@@ -938,6 +965,7 @@ def session_list_html(
938
965
  limit (int, optional): Limit for the number of sessions returned. Defaults to 50.
939
966
  offset (int, optional): Offset for the session list. Defaults to 0.
940
967
 
968
+
941
969
  Returns:
942
970
  str: HTML-formatted table containing the list of user sessions.
943
971
  """
@@ -61,6 +61,8 @@ class TestALIndividual(unittest.TestCase):
61
61
  # Assigning this_thread to self.individual
62
62
  self.individual.this_thread = self.this_thread
63
63
 
64
+ self.individual.name.first = "John"
65
+
64
66
  def test_phone_numbers(self):
65
67
  self.assertEqual(self.individual.phone_numbers(), "")
66
68
  self.individual.phone_number = ""
@@ -423,9 +425,7 @@ class TestALIndividual(unittest.TestCase):
423
425
  self.assertEqual(self.individual.pronoun_possessive("fish"), "xem fish")
424
426
 
425
427
  self.individual.pronouns_self_described = "Xe/Xir/Xirs/xem/xirself"
426
- # Should raise an exception
427
- with self.assertRaises(DAAttributeError):
428
- self.individual.pronoun_objective()
428
+ self.assertEqual(self.individual.pronoun_objective(), "John")
429
429
 
430
430
  def test_name_methods(self):
431
431
  self.individual.name.first = "John"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docassemble.AssemblyLine
3
- Version: 3.0.1
3
+ Version: 3.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
@@ -44,7 +44,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d
44
44
  return out
45
45
 
46
46
  setup(name='docassemble.AssemblyLine',
47
- version='3.0.1',
47
+ version='3.1.0',
48
48
  description=(''),
49
49
  long_description='# Suffolk LIT Lab Document Assembly Line\r\n\r\n[![PyPI version](https://badge.fury.io/py/docassemble-AssemblyLine.svg)](https://badge.fury.io/py/docassemble-AssemblyLine)\r\n\r\n<img src="https://user-images.githubusercontent.com/7645641/142245862-c2eb02ab-3090-4e97-9653-bb700bf4c54d.png" alt="drawing" width="300" alt="work together" style="align: center;"/>\r\n\r\nThe Assembly Line Project is a collection of volunteers, students, and institutions who joined together\r\nduring the COVID-19 pandemic to help increase access to the court system. Our vision is mobile-friendly,\r\neasy to use **guided** online forms that help empower litigants to access the court remotely.\r\n\r\nOur signature project is [CourtFormsOnline.org](https://courtformsonline.org).\r\n\r\nWe designed a step-by-step, assembly line style process for automating court forms on top of Docassemble\r\nand built several tools along the way that **you** can use in your home jurisdiction.\r\n\r\nThis package contains **runtime code** and **pre-written questions** to support authoring robust, \r\nconsistent, and attractive Docassemble interviews that help complete court forms.\r\n\r\nRead more on our [documentation page](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/).\r\n\r\n\r\n# Related repositories\r\n\r\n* https://github.com/SuffolkLitLab/docassemble-ALWeaver\r\n* https://github.com/SuffolkLitLab/docassemble-ALMassachusetts\r\n* https://github.com/SuffolkLitLab/docassemble-MassAccess\r\n* https://github.com/SuffolkLitLab/docassemble-ALThemeTemplate\r\n* https://github.com/SuffolkLitLab/EfileProxyServer\r\n\r\n# Documentation\r\n\r\nhttps://suffolklitlab.org/docassemble-AssemblyLine-documentation/\r\n\r\n# Installation\r\n\r\n## Menu-driven installation\r\n\r\nThe recommended installation method is with the [guided installation script](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation).\r\n\r\n## Manual installation\r\n\r\nNormally you do not need to manually install the Assembly Line. Use the [installation script](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation)\r\nif you can. The instructions below are for your optional reference.\r\n\r\nThis package depends on the following configuration changes on your Docassemble server:\r\n\r\n* Ensure that [font-awesome](https://docassemble.org/docs/config.html#default%20icons) is enabled (this may be on by default:\r\n```yaml\r\ndefault icons: font awesome\r\n```\r\n* Add a [Google API key](https://docassemble.org/docs/config.html#google) that has access to:\r\n * Google Places API\r\n * Google Geocoding API\r\n* Add a [VoiceRSS API key](https://docassemble.org/docs/config.html#voicerss)\r\n* Add a [Twilio API key](https://docassemble.org/docs/config.html#twilio) for SMS support\r\n* Add an email account: [Mailgun](https://docassemble.org/docs/config.html#mailgun%20api) or [SendGrid](https://docassemble.org/docs/config.html#sendgrid%20api) recommended for email support\r\n* To show package update time and to enable the feedback form, add a GitHub Private Access token to your config.yml file, like this:\r\n```yaml\r\n# Needs access to create new issues on repositories\r\ngithub issues:\r\n username: "suffolklitlab-issues"\r\n token: "12345"\r\n# Does not need any special access to public repositories \r\ngithub readonly:\r\n username: "suffolklitlab-issues"\r\n password: "45678"\r\n type: "basic"\r\n```\r\n* If you are also using the [Assembly Line Weaver](https://github.com/SuffolkLITLab/docassemble-assemblylinewizard), you may want to set up a [Docassemble API key](https://docassemble.org/docs/api.html#manage_api) in your config.yml file to allow you to install packages automatically, like this:\r\n```yaml\r\ninstall packages api key: 12345\r\n```\r\n\r\n# Migration\r\n\r\nSee [discussion here](https://github.com/SuffolkLITLab/docassemble-AssemblyLine/issues/69)\r\n\r\n\r\n# ALDocument class\r\n\r\n## Purpose\r\n\r\nThe ALDocument class is a small utility library that makes it simpler to use the following features in an interview:\r\n\r\n* Conditional assembly of multiple, optional documents that are triggered in different ways in your interview\r\n* An addendum for PDF files that makes it simple to deal with overflow text\r\n* A customizable download screen that lists the documents in a neat table\r\n* A customizable "send" button that allows the user to email the final forms to a location of their choice\r\n\r\nHere is a small snippet that you can copy and modify that shows how to use the most important features of the ALDocument class.\r\n\r\n```\r\n---\r\nobjects:\r\n - CRA_Motion_to_Dismiss_attachment: ALDocument.using(filename="CRA_Motion_to_Dismiss", title="Motion to Dismiss CRA", enabled=True, has_addendum=True, default_overflow_message="[See addendum]") \r\n---\r\nobjects:\r\n - al_user_bundle: ALDocumentBundle.using(elements=[CRA_Motion_to_Dismiss_attachment], title="Forms to download and deliver to court", filename="motion_to_dismiss_CRA.pdf")\r\n - al_court_bundle: ALDocumentBundle.using(elements=[CRA_Motion_to_Dismiss_attachment], title="Forms to download and deliver to court", filename="motion_to_dismiss_CRA.pdf")\r\n---\r\ngeneric object: ALDocument\r\nattachment:\r\n variable name: x.addendum\r\n docx template file: docx_addendum.docx\r\n---\r\ncode: |\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields[\'reasons_for_request\'].overflow_trigger = 640\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields[\'reasons_for_request\'].label = "Reasons for request"\r\n CRA_Motion_to_Dismiss_attachment.overflow_fields.gathered = True\r\n \r\n---\r\nattachment:\r\n variable name: CRA_Motion_to_Dismiss_attachment[i]\r\n name: CRA Motion to Dismiss\r\n filename: CRA_Motion_to_Dismiss\r\n skip undefined: True\r\n pdf template file: CRA_Motion_to_Dismiss.pdf\r\n fields: \r\n - "court_county": ${ trial_court.address.county }\r\n - "docket_number": ${ docket_number }\r\n - "user_signature": ${ users[0].signature_if_final(i) }\r\n - "signature_date": ${ signature_date }\r\n```\r\n\r\nIt is very common to have a *contingent* document in ALDocument. If your document is contingent, remove the `enabled=True` from the object declaration, and use\r\nsome other method to "turn on" the attachment.\r\n\r\nE.g.,\r\n\r\n```\r\ncode: |\r\n CRA_Motion_to_Dismiss_attachment.enabled = condition1 and condition2\r\n```\r\n\r\n# Changelog\r\n\r\nSee [CHANGELOG.MD](https://github.com/SuffolkLITLab/docassemble-AssemblyLine/blob/main/CHANGELOG.md)\r\n',
50
50
  long_description_content_type='text/markdown',
@@ -1 +0,0 @@
1
- __version__ = '3.0.1'