Coverage for project/models/project.py: 100.00%

80 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-06-06 22:17 +0000

1import uuid 

2 

3from django.db import models, transaction 

4from django.forms.models import model_to_dict 

5from django.utils import timezone 

6 

7from core.models import core as core_models 

8from core.models import user as core_user_models 

9from . import git_repository as git_repository_models 

10from . import issue as issue_models 

11 

12 

13class ProjectLabelData(core_models.CoreModel): 

14 label = models.CharField(max_length=255) 

15 description = models.TextField(blank=True, null=True, default="") 

16 color = models.CharField(max_length=7, default="#000000") 

17 

18 

19class ProjectLabel(core_models.CoreModel): 

20 current = models.ForeignKey('ProjectLabelData', on_delete=models.CASCADE) 

21 

22 

23class ProjectData(core_models.CoreModel): 

24 name = models.CharField(max_length=255) 

25 description = models.TextField(blank=True, null=True, default="") 

26 start_date = models.DateField(default=timezone.now) 

27 end_date = models.DateField(blank=True, null=True) 

28 is_active = models.BooleanField(default=True) 

29 is_private = models.BooleanField(default=False) 

30 

31 

32class ProjectActiveManager(models.Manager): 

33 def get_queryset(self): 

34 return super().get_queryset().filter(deleted=None).filter(current__is_active=True) 

35 

36 

37class Project(core_models.CoreModel): 

38 class Meta: 

39 ordering = ['current__name'] 

40 

41 active_objects = ProjectActiveManager() 

42 

43 current = models.ForeignKey(ProjectData, on_delete=models.CASCADE) 

44 

45 label = models.ForeignKey(ProjectLabel, on_delete=models.CASCADE, blank=True, null=True) 

46 git_repositories = models.ManyToManyField(git_repository_models.GitRepository) 

47 users = models.ManyToManyField('core.CoreUser') 

48 

49 def update_project_data(self, user_id: uuid.UUID, project_data: ProjectData) -> 'Project': 

50 """ 

51 Updates a project's data. This is a helper function to illustrate the use of `current` retention since we do not delete data. 

52 

53 Args: 

54 user_id (uuid): The UUID of the user updating the project. 

55 project_data (dictionary): The data to update in a project. 

56 

57 Returns: 

58 Project: The updated project. 

59 """ 

60 

61 with transaction.atomic(): 

62 current_project_data = model_to_dict(self.current) 

63 

64 new_project_data = {} 

65 new_project_data['created_by_id'] = user_id 

66 new_project_data['name'] = project_data.get('name', current_project_data.get('name', '')) 

67 new_project_data['description'] = project_data.get('description', current_project_data.get('description', '')) 

68 new_project_data['start_date'] = project_data.get('start_date', current_project_data.get('start_date', '')) 

69 new_project_data['end_date'] = project_data.get('end_date', current_project_data.get('end_date', '')) 

70 new_project_data['is_active'] = project_data.get('is_active', current_project_data.get('is_active', '')) 

71 new_project_data['is_private'] = project_data.get('is_private', current_project_data.get('is_private', '')) 

72 new_project_data = ProjectData(**new_project_data) 

73 new_project_data.save() 

74 

75 new_project_data.save() 

76 self.current = new_project_data 

77 self.save() 

78 return self 

79 

80 def update_git_repositories(self, git_repositories: list) -> 'Project': 

81 """ 

82 A helper method to update the git repositories that may use this project. 

83 

84 Args: 

85 git_repositories (list): A list of UUIDs of git repository objects to be shown with this project. 

86 

87 Returns: 

88 Project: The updated project. 

89 """ 

90 self.git_repositories.set(git_repositories) 

91 self.save() 

92 

93 return self 

94 

95 def update_users(self, users: list) -> 'Project': 

96 """ 

97 A helper method to update the users that have access to this project. 

98 

99 Args: 

100 users (list): A list of UUIDs of users to be shown with this project. 

101 

102 Returns: 

103 Project: The updated project 

104 """ 

105 

106 self.users.set(users) 

107 self.save() 

108 

109 return self 

110 

111 def generate_label(self): 

112 """ 

113 A helper method to generate a project label based on the project's name. 

114 

115 Returns: 

116 str: A hyphenated label based on the project's name. 

117 """ 

118 

119 return "-".join(self.current.name.split()).lower() 

120 

121 def update_project_label(self, user_id: uuid.UUID, new_project_label: ProjectLabel): 

122 """ 

123 A helper method to update a project label. 

124 

125 Args: 

126 user_id (uuid.UUID): The logged in user that is updating the label. 

127 new_project_label (ProjectLabel): The new project label, containing ProjectLabelData. 

128 

129 Returns: 

130 project (Project): The updated project. 

131 """ 

132 

133 new_project_label_data = ProjectLabelData(created_by_id=user_id, **new_project_label.get('current')) 

134 new_project_label_data.save() 

135 new_label = ProjectLabel(created_by_id=user_id, current=new_project_label_data) 

136 new_label.save() 

137 self.label = new_label 

138 self.save() 

139 

140 return self 

141 

142 def list_users(self): 

143 """ 

144 A helper method to list the users that could potentially access this project. Based on the logged in user's organization and this project. 

145 

146 Returns: 

147 users (list): The list of users. 

148 """ 

149 

150 # Get unique users from owning organization and this project 

151 organization_data = self.organizationprojects_set.first() 

152 if organization_data: 

153 organization_users = organization_data.members.values_list('id', flat=True) 

154 else: 

155 organization_users = [] 

156 project_users = self.users.values_list('id', flat=True) 

157 # Combine the user IDs and get distinct users 

158 user_ids = set(organization_users).union(set(project_users)) 

159 return core_user_models.CoreUser.objects.filter(id__in=user_ids) 

160 

161 def list_issues(self): 

162 """ 

163 A helper method to list the project's issues. 

164 

165 Returns: 

166 issues (list): The list of issues for the project. 

167 """ 

168 

169 # Get the issues from the issue data for the project - there could be a lot of issue data for a single issue so we have to pare it down with a set, could try distinct here for performance when needed. 

170 issue_ids = set() 

171 issue_datas = self.issuedata_set.values_list('issue', flat=True).filter(project=self) 

172 issue_ids.update(issue_datas) 

173 return issue_models.Issue.objects.filter(id__in=issue_ids) 

174 

175 def __str__(self): 

176 potential_names = [] 

177 if self.current.name: 

178 potential_names.append(self.current.name) 

179 if self.current.label: 

180 potential_names.append(f"- ({self.current.label})") 

181 return self.current.name