diff --git a/taiga/projects/models.py b/taiga/projects/models.py index 46853b96..1e8eb2f4 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -940,9 +940,12 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): "points": getattr(project.default_points, "name", None), "epic_status": getattr(project.default_epic_status, "name", None), "us_status": getattr(project.default_us_status, "name", None), + "us_duedate": getattr(project.default_us_duedate, "name", None), "task_status": getattr(project.default_task_status, "name", None), + "task_duedate": getattr(project.default_task_duedate, "name", None), "issue_status": getattr(project.default_issue_status, "name", None), "issue_type": getattr(project.default_issue_type, "name", None), + "issue_duedate": getattr(project.default_issue_duedate, "name", None), "priority": getattr(project.default_priority, "name", None), "severity": getattr(project.default_severity, "name", None) } @@ -969,6 +972,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): "order": us_status.order, }) + self.us_duedates = [] + for us_duedate in project.us_duedates.all(): + self.us_duedates.append({ + "name": us_duedate.name, + "slug": us_duedate.slug, + "by_default": us_duedate.by_default, + "color": us_duedate.color, + "days_to_due": us_duedate.days_to_due, + "order": us_duedate.order, + }) + self.points = [] for us_point in project.points.all(): self.points.append({ @@ -987,6 +1001,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): "order": task_status.order, }) + self.task_duedates = [] + for task_duedate in project.task_duedates.all(): + self.task_duedates.append({ + "name": task_duedate.name, + "slug": task_duedate.slug, + "by_default": task_duedate.by_default, + "color": task_duedate.color, + "days_to_due": task_duedate.days_to_due, + "order": task_duedate.order, + }) + self.issue_statuses = [] for issue_status in project.issue_statuses.all(): self.issue_statuses.append({ @@ -1005,6 +1030,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): "order": issue_type.order, }) + self.issue_duedates = [] + for issue_duedate in project.issue_duedates.all(): + self.issue_duedates.append({ + "name": issue_duedate.name, + "slug": issue_duedate.slug, + "by_default": issue_duedate.by_default, + "color": issue_duedate.color, + "days_to_due": issue_duedate.days_to_due, + "order": issue_duedate.order, + }) + self.priorities = [] for priority in project.priorities.all(): self.priorities.append({ @@ -1116,6 +1152,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): project=project ) + for us_duedate in self.us_duedates: + UserStoryDueDate.objects.create( + name=us_duedate["name"], + slug=us_duedate["slug"], + by_default=us_duedate["by_default"], + color=us_duedate["color"], + days_to_due=us_duedate["days_to_due"], + order=us_duedate["order"], + project=project + ) + for point in self.points: Points.objects.create( name=point["name"], @@ -1134,6 +1181,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): project=project ) + for task_duedate in self.task_duedates: + TaskDueDate.objects.create( + name=task_duedate["name"], + slug=task_duedate["slug"], + by_default=task_duedate["by_default"], + color=task_duedate["color"], + days_to_due=task_duedate["days_to_due"], + order=task_duedate["order"], + project=project + ) + for issue_status in self.issue_statuses: IssueStatus.objects.create( name=issue_status["name"], @@ -1152,6 +1210,17 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): project=project ) + for issue_duedate in self.issue_duedates: + IssueDueDate.objects.create( + name=issue_duedate["name"], + slug=issue_duedate["slug"], + by_default=issue_duedate["by_default"], + color=issue_duedate["color"], + days_to_due=issue_duedate["days_to_due"], + order=issue_duedate["order"], + project=project + ) + for priority in self.priorities: Priority.objects.create( name=priority["name"], @@ -1185,6 +1254,9 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): if self.us_statuses: project.default_us_status = UserStoryStatus.objects.get(name=self.default_options["us_status"], project=project) + if self.us_duedates: + project.default_us_duedate = UserStoryDueDate.objects.get(name=self.default_options["us_duedates"], + project=project) if self.points: project.default_points = Points.objects.get(name=self.default_options["points"], project=project) @@ -1192,10 +1264,15 @@ class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): if self.task_statuses: project.default_task_status = TaskStatus.objects.get(name=self.default_options["task_status"], project=project) + if self.task_duedates: + project.default_task_duedate = TaskDueDate.objects.get(name=self.default_options["task_duedates"], + project=project) if self.issue_statuses: project.default_issue_status = IssueStatus.objects.get(name=self.default_options["issue_status"], project=project) - + if self.issue_duedates: + project.default_issue_duedate = TaskDueDate.objects.get(name=self.default_options["issue_duedates"], + project=project) if self.issue_types: project.default_issue_type = IssueType.objects.get(name=self.default_options["issue_type"], project=project) diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index 64d36fc3..373c1b3b 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -37,6 +37,17 @@ from .notifications.choices import NotifyLevel # Custom values for selectors ###################################################### +class BaseDueDateSerializer(serializers.LightSerializer): + id = Field() + name = I18NField() + slug = Field() + order = Field() + by_default = Field() + days_to_due = Field() + color = Field() + project = Field(attr="project_id") + + class EpicStatusSerializer(serializers.LightSerializer): id = Field() name = I18NField() @@ -67,17 +78,6 @@ class PointsSerializer(serializers.LightSerializer): project = Field(attr="project_id") -class BaseDueDateSerializer(serializers.LightSerializer): - id = Field() - name = I18NField() - slug = Field() - order = Field() - by_default = Field() - days_to_due = Field() - color = Field() - project = Field(attr="project_id") - - class UserStoryDueDateSerializer(BaseDueDateSerializer): pass @@ -379,10 +379,13 @@ class ProjectSerializer(serializers.LightSerializer): class ProjectDetailSerializer(ProjectSerializer): epic_statuses = Field(attr="epic_statuses_attr") us_statuses = Field(attr="userstory_statuses_attr") + us_duedates = Field(attr="userstory_duedates_attr") points = Field(attr="points_attr") task_statuses = Field(attr="task_statuses_attr") + task_duedates = Field(attr="task_duedates_attr") issue_statuses = Field(attr="issue_statuses_attr") issue_types = Field(attr="issue_types_attr") + issue_duedates = Field(attr="issue_duedates_attr") priorities = Field(attr="priorities_attr") severities = Field(attr="severities_attr") epic_custom_attributes = Field(attr="epic_custom_attributes_attr") @@ -413,10 +416,15 @@ class ProjectDetailSerializer(ProjectSerializer): def to_value(self, instance): # Name attributes must be translated - for attr in ["epic_statuses_attr", "userstory_statuses_attr", "points_attr", "task_statuses_attr", - "issue_statuses_attr", "issue_types_attr", "priorities_attr", "severities_attr", - "epic_custom_attributes_attr", "userstory_custom_attributes_attr", - "task_custom_attributes_attr", "issue_custom_attributes_attr", "roles_attr"]: + for attr in ["epic_statuses_attr", "userstory_statuses_attr", + "userstory_duedates_attr", "points_attr", + "task_statuses_attr", "task_duedates_attr", + "issue_statuses_attr", "issue_types_attr", + "issue_duedates_attr", "priorities_attr", + "severities_attr", "epic_custom_attributes_attr", + "userstory_custom_attributes_attr", + "task_custom_attributes_attr", + "issue_custom_attributes_attr", "roles_attr"]: assert hasattr(instance, attr), "instance must have a {} attribute".format(attr) val = getattr(instance, attr) diff --git a/taiga/projects/utils.py b/taiga/projects/utils.py index ed26647c..39d246eb 100644 --- a/taiga/projects/utils.py +++ b/taiga/projects/utils.py @@ -167,6 +167,29 @@ def attach_userstory_statuses(queryset, as_field="userstory_statuses_attr"): return queryset +def attach_userstory_duedates(queryset, as_field="userstory_duedates_attr"): + """Attach a json userstory duedates representation to each object of the queryset. + + :param queryset: A Django projects queryset object. + :param as_field: Attach the userstory duedates as an attribute with this name. + + :return: Queryset object with the additional `as_field` field. + """ + model = queryset.model + sql = """ + SELECT json_agg( + row_to_json(projects_userstoryduedate) + ORDER BY projects_userstoryduedate.order + ) + FROM projects_userstoryduedate + WHERE projects_userstoryduedate.project_id = {tbl}.id + """ + + sql = sql.format(tbl=model._meta.db_table) + queryset = queryset.extra(select={as_field: sql}) + return queryset + + def attach_points(queryset, as_field="points_attr"): """Attach a json points representation to each object of the queryset. @@ -213,6 +236,29 @@ def attach_task_statuses(queryset, as_field="task_statuses_attr"): return queryset +def attach_task_duedates(queryset, as_field="task_duedates_attr"): + """Attach a json task duedates representation to each object of the queryset. + + :param queryset: A Django projects queryset object. + :param as_field: Attach the task duedates as an attribute with this name. + + :return: Queryset object with the additional `as_field` field. + """ + model = queryset.model + sql = """ + SELECT json_agg( + row_to_json(projects_taskduedate) + ORDER BY projects_taskduedate.order + ) + FROM projects_taskduedate + WHERE projects_taskduedate.project_id = {tbl}.id + """ + + sql = sql.format(tbl=model._meta.db_table) + queryset = queryset.extra(select={as_field: sql}) + return queryset + + def attach_issue_statuses(queryset, as_field="issue_statuses_attr"): """Attach a json issue statuses representation to each object of the queryset. @@ -259,6 +305,29 @@ def attach_issue_types(queryset, as_field="issue_types_attr"): return queryset +def attach_issue_duedates(queryset, as_field="issue_duedates_attr"): + """Attach a json issue duedates representation to each object of the queryset. + + :param queryset: A Django projects queryset object. + :param as_field: Attach the duedates as an attribute with this name. + + :return: Queryset object with the additional `as_field` field. + """ + model = queryset.model + sql = """ + SELECT json_agg( + row_to_json(projects_issueduedate) + ORDER BY projects_issueduedate.order + ) + FROM projects_issueduedate + WHERE projects_issueduedate.project_id = {tbl}.id + """ + + sql = sql.format(tbl=model._meta.db_table) + queryset = queryset.extra(select={as_field: sql}) + return queryset + + def attach_priorities(queryset, as_field="priorities_attr"): """Attach a json priorities representation to each object of the queryset. @@ -530,9 +599,12 @@ def attach_extra_info(queryset, user=None): queryset = attach_notify_policies(queryset) queryset = attach_epic_statuses(queryset) queryset = attach_userstory_statuses(queryset) + queryset = attach_userstory_duedates(queryset) queryset = attach_points(queryset) queryset = attach_task_statuses(queryset) + queryset = attach_task_duedates(queryset) queryset = attach_issue_statuses(queryset) + queryset = attach_issue_duedates(queryset) queryset = attach_issue_types(queryset) queryset = attach_priorities(queryset) queryset = attach_severities(queryset)