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
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-06 22:17 +0000
1import uuid
3from django.db import models, transaction
4from django.forms.models import model_to_dict
5from django.utils import timezone
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
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")
19class ProjectLabel(core_models.CoreModel):
20 current = models.ForeignKey('ProjectLabelData', on_delete=models.CASCADE)
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)
32class ProjectActiveManager(models.Manager):
33 def get_queryset(self):
34 return super().get_queryset().filter(deleted=None).filter(current__is_active=True)
37class Project(core_models.CoreModel):
38 class Meta:
39 ordering = ['current__name']
41 active_objects = ProjectActiveManager()
43 current = models.ForeignKey(ProjectData, on_delete=models.CASCADE)
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')
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.
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.
57 Returns:
58 Project: The updated project.
59 """
61 with transaction.atomic():
62 current_project_data = model_to_dict(self.current)
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()
75 new_project_data.save()
76 self.current = new_project_data
77 self.save()
78 return self
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.
84 Args:
85 git_repositories (list): A list of UUIDs of git repository objects to be shown with this project.
87 Returns:
88 Project: The updated project.
89 """
90 self.git_repositories.set(git_repositories)
91 self.save()
93 return self
95 def update_users(self, users: list) -> 'Project':
96 """
97 A helper method to update the users that have access to this project.
99 Args:
100 users (list): A list of UUIDs of users to be shown with this project.
102 Returns:
103 Project: The updated project
104 """
106 self.users.set(users)
107 self.save()
109 return self
111 def generate_label(self):
112 """
113 A helper method to generate a project label based on the project's name.
115 Returns:
116 str: A hyphenated label based on the project's name.
117 """
119 return "-".join(self.current.name.split()).lower()
121 def update_project_label(self, user_id: uuid.UUID, new_project_label: ProjectLabel):
122 """
123 A helper method to update a project label.
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.
129 Returns:
130 project (Project): The updated project.
131 """
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()
140 return self
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.
146 Returns:
147 users (list): The list of users.
148 """
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)
161 def list_issues(self):
162 """
163 A helper method to list the project's issues.
165 Returns:
166 issues (list): The list of issues for the project.
167 """
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)
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