Coverage for middle_layer/allocate/domain_layer/entities/tac_member.py: 74.42%

43 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2026-04-13 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 

18from sqlalchemy import ForeignKey 

19from sqlalchemy.orm import Mapped, mapped_column, relationship 

20 

21from common.domain_layer import JSON_OBJECT 

22from common.domain_layer.entities.base import Base 

23from solicit.domain_layer.entities.solicitation import Solicitation 

24 

25 

26class TACMember(Base): 

27 __tablename__ = "tac_members" 

28 """A group of people who are tasked with collectively making recommendations to the NRAO/GBO regarding time 

29 allocation and scheduling priorities of proposals submitted to the Observatory each semester, weighing science 

30 merit, technical feasibility, and programmatic concerns 

31 """ 

32 

33 tac_member_id: Mapped[int] = mapped_column(primary_key=True) 

34 solicitation_id: Mapped[int] = mapped_column(ForeignKey("solicitations.solicitation_id", ondelete="CASCADE")) 

35 solicitation: Mapped[Solicitation] = relationship(Solicitation, back_populates="tac_members") 

36 user_id: Mapped[int] = mapped_column(nullable=False) 

37 first_name: Mapped[str] = mapped_column(nullable=False) 

38 last_name: Mapped[str] = mapped_column(nullable=False) 

39 is_chair: Mapped[bool] = mapped_column(nullable=False, server_default="False") 

40 is_active: Mapped[bool] = mapped_column(nullable=False, server_default="False") 

41 

42 def __init__( 

43 self, 

44 solicitation: Solicitation, 

45 user_id: int, 

46 first_name: str, 

47 last_name: str, 

48 is_chair: bool = False, 

49 is_active: bool = False, 

50 ): 

51 # Validate solicitation 

52 if not solicitation or not isinstance(solicitation, Solicitation): 

53 raise ValueError("A valid Solicitation object is required") 

54 if not hasattr(solicitation, "solicitation_id") or not solicitation.solicitation_id: 

55 raise ValueError("Solicitation must have a valid solicitation_id") 

56 

57 # Validate user_id 

58 if not isinstance(user_id, int): 

59 raise ValueError(f"User ID must be an integer, got {type(user_id).__name__}") 

60 if user_id <= 0: 

61 raise ValueError(f"User ID must be a positive integer, got {user_id}") 

62 

63 # Validate first_name 

64 if not isinstance(first_name, str): 

65 raise ValueError(f"First name must be a string, got {type(first_name).__name__}") 

66 if not first_name.strip(): 

67 raise ValueError("First name cannot be empty") 

68 

69 # Validate last_name 

70 if not isinstance(last_name, str): 

71 raise ValueError(f"Last name must be a string, got {type(last_name).__name__}") 

72 if not last_name.strip(): 

73 raise ValueError("Last name cannot be empty") 

74 

75 # Validate boolean fields 

76 if not isinstance(is_chair, bool): 

77 raise ValueError(f"is_chair must be a boolean, got {type(is_chair).__name__}") 

78 if not isinstance(is_active, bool): 

79 raise ValueError(f"is_active must be a boolean, got {type(is_active).__name__}") 

80 

81 # All validations passed, proceed with initialization 

82 super().__init__( 

83 solicitation=solicitation, 

84 solicitation_id=solicitation.solicitation_id, 

85 user_id=user_id, 

86 first_name=first_name.strip(), 

87 last_name=last_name.strip(), 

88 is_chair=is_chair, 

89 is_active=is_active, 

90 ) 

91 

92 # Ensure the relationship is properly established 

93 if not hasattr(solicitation, "tac_members"): 

94 raise ValueError("Solicitation object is missing the tac_members attribute") 

95 solicitation.tac_members.append(self) 

96 

97 def __eq__(self, other: object) -> bool: 

98 """Use Solicitation.solicitation_id to avoid RecursionError""" 

99 return isinstance(other, TACMember) and ( 

100 self.tac_member_id, 

101 self.solicitation_id, 

102 self.user_id, 

103 self.first_name, 

104 self.last_name, 

105 self.is_chair, 

106 self.is_active, 

107 ) == ( 

108 other.tac_member_id, 

109 other.solicitation_id, 

110 other.user_id, 

111 other.first_name, 

112 other.last_name, 

113 other.is_chair, 

114 other.is_active, 

115 )