Coverage for middle_layer/solicit/domain_layer/entities/default_instruction.py: 97.50%

40 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2026-03-09 06:13 +0000

1# Copyright 2024 Associated Universities, Inc. 

2# 

3# This file is part of Telescope Time Allocation Tools (TTAT). 

4# 

5# TTAT is free software: you can redistribute it and/or modify 

6# it under the terms of the GNU General Public License as published by 

7# the Free Software Foundation, either version 3 of the License, or 

8# any later version. 

9# 

10# TTAT is distributed in the hope that it will be useful, 

11# but WITHOUT ANY WARRANTY; without even the implied warranty of 

12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

13# GNU General Public License for more details. 

14# 

15# You should have received a copy of the GNU General Public License 

16# along with TTAT. If not, see <https://www.gnu.org/licenses/>. 

17# 

18# pyright: reportImportCycles=false 

19import typing 

20from typing import override 

21 

22from sqlalchemy import ForeignKey 

23from sqlalchemy.orm import Mapped, mapped_column, relationship 

24 

25from common.domain_layer import JSON_OBJECT 

26from common.domain_layer.entities.base import Base 

27 

28if typing.TYPE_CHECKING: 

29 from solicit.domain_layer.entities.solicitation import ProposalProcess, SolicitationProposalProcess 

30 

31 

32class DefaultInstruction(Base): 

33 __tablename__: str = "default_instructions" 

34 """ 

35 Default instructions that are unique per ProposalProcess. 

36 Used to provide templated instructional text that can be referenced by shortcode in algorithms. 

37 """ 

38 

39 default_instruction_id: Mapped[int] = mapped_column(primary_key=True) 

40 proposal_process_id: Mapped[int] = mapped_column( 

41 ForeignKey("proposal_processes.proposal_process_id", ondelete="CASCADE") 

42 ) 

43 shortcode: Mapped[str] = mapped_column(nullable=False, unique=True) 

44 dropdown_name: Mapped[str] = mapped_column(nullable=False) 

45 instruction_text: Mapped[str] = mapped_column(nullable=False) 

46 

47 # Relationship to parent proposal process 

48 proposal_process: Mapped["ProposalProcess"] = relationship("ProposalProcess", back_populates="default_instructions") 

49 

50 

51class Instruction(Base): 

52 __tablename__: str = "instructions" 

53 """Lists the instructions to be displayed at various points in the proposal process, specific to the solicitation.""" 

54 instruction_id: Mapped[int] = mapped_column(primary_key=True) 

55 instruction_text: Mapped[str] = mapped_column(nullable=False) 

56 

57 default_instruction_id: Mapped[int] = mapped_column( 

58 ForeignKey("default_instructions.default_instruction_id", ondelete="SET NULL"), nullable=True 

59 ) 

60 default_instruction: Mapped["DefaultInstruction"] = relationship("DefaultInstruction") 

61 

62 solicitation_proposal_process_id: Mapped[int] = mapped_column( 

63 ForeignKey("solicitation_proposal_processes.solicitation_proposal_process_id", ondelete="CASCADE") 

64 ) 

65 

66 solicitation_proposal_process: Mapped["SolicitationProposalProcess"] = relationship( 

67 "SolicitationProposalProcess", back_populates="instructions" 

68 ) 

69 

70 @property 

71 def shortcode(self) -> str | None: 

72 """Get the shortcode from the related default instruction""" 

73 return self.default_instruction.shortcode if self.default_instruction else None 

74 

75 @property 

76 def dropdown_name(self) -> str | None: 

77 """Get the dropdown_name from the related default instruction""" 

78 return self.default_instruction.dropdown_name if self.default_instruction else None 

79 

80 @classmethod 

81 def from_default( 

82 cls, 

83 solicitation_proposal_process: "SolicitationProposalProcess", 

84 default_instruction: DefaultInstruction, 

85 instruction_text: str | None = None, 

86 ): 

87 """ 

88 Create an Instruction from a DefaultInstruction 

89 

90 Args: 

91 solicitation_proposal_process: the solicitation proposal process that this instruction will be associated with 

92 default_instruction: DefaultInstruction instance 

93 instruction_text: Optional custom instruction text. If None, uses the default instruction text 

94 

95 Returns: 

96 A new Instruction instance 

97 """ 

98 default_instruction_id = default_instruction.default_instruction_id 

99 default_text = default_instruction.instruction_text 

100 

101 return cls( 

102 solicitation_proposal_process=solicitation_proposal_process, 

103 default_instruction_id=default_instruction_id, 

104 instruction_text=instruction_text if instruction_text is not None else default_text, 

105 ) 

106 

107 @override 

108 def __json__(self) -> JSON_OBJECT: 

109 return super().__json__() | { 

110 "shortcode": self.shortcode, 

111 "dropdownName": self.dropdown_name, 

112 }