Added due_date to taks, us and issues
parent
8b76b5787b
commit
c2149ef6a5
|
@ -0,0 +1,28 @@
|
|||
# Copyright (C) 2018 Miguel González <migonzalvar@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class DueDateMixin(models.Model):
|
||||
due_date = models.DateField(
|
||||
blank=True, null=True, default=None, verbose_name=_('due date'),
|
||||
)
|
||||
due_date_reason = models.TextField(
|
||||
null=False, blank=True, default='', verbose_name=_('reason for the due date'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright (C) 2018 Miguel González <migonzalvar@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import datetime as dt
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from taiga.base.api import serializers
|
||||
from taiga.base.fields import Field, MethodField
|
||||
|
||||
|
||||
class DueDateSerializerMixin(serializers.LightSerializer):
|
||||
due_date = Field()
|
||||
due_date_reason = Field()
|
||||
due_date_status = MethodField()
|
||||
|
||||
THRESHOLD = 3
|
||||
|
||||
def get_due_date_status(self, obj):
|
||||
if obj.due_date is None:
|
||||
return 'not_set'
|
||||
elif obj.status.is_closed:
|
||||
return 'no_longer_applicable'
|
||||
elif timezone.now().date() > obj.due_date:
|
||||
return 'past_due'
|
||||
elif (timezone.now().date() + dt.timedelta(days=self.THRESHOLD)) >= obj.due_date:
|
||||
return 'due_soon'
|
||||
else:
|
||||
return 'set'
|
|
@ -357,6 +357,8 @@ def userstory_freezer(us) -> dict:
|
|||
"blocked_note_html": mdrender(us.project, us.blocked_note),
|
||||
"custom_attributes": extract_user_story_custom_attributes(us),
|
||||
"tribe_gig": us.tribe_gig,
|
||||
"due_date": str(us.due_date),
|
||||
"due_date_reason": str(us.due_date_reason),
|
||||
}
|
||||
|
||||
return snapshot
|
||||
|
@ -381,6 +383,8 @@ def issue_freezer(issue) -> dict:
|
|||
"blocked_note": issue.blocked_note,
|
||||
"blocked_note_html": mdrender(issue.project, issue.blocked_note),
|
||||
"custom_attributes": extract_issue_custom_attributes(issue),
|
||||
"due_date": str(issue.due_date),
|
||||
"due_date_reason": str(issue.due_date_reason),
|
||||
}
|
||||
|
||||
return snapshot
|
||||
|
@ -406,6 +410,8 @@ def task_freezer(task) -> dict:
|
|||
"blocked_note": task.blocked_note,
|
||||
"blocked_note_html": mdrender(task.project, task.blocked_note),
|
||||
"custom_attributes": extract_task_custom_attributes(task),
|
||||
"due_date": str(task.due_date),
|
||||
"due_date_reason": str(task.due_date_reason),
|
||||
}
|
||||
|
||||
return snapshot
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2018-04-09 09:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('issues', '0007_auto_20160614_1201'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='due_date',
|
||||
field=models.DateField(blank=True, default=None, null=True, verbose_name='due date'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='due_date_reason',
|
||||
field=models.TextField(blank=True, default='', verbose_name='reason for the due date'),
|
||||
),
|
||||
]
|
|
@ -24,13 +24,14 @@ from django.utils import timezone
|
|||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from taiga.projects.due_dates.models import DueDateMixin
|
||||
from taiga.projects.occ import OCCModelMixin
|
||||
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||
from taiga.projects.mixins.blocked import BlockedMixin
|
||||
from taiga.projects.tagging.models import TaggedMixin
|
||||
|
||||
|
||||
class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
||||
class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateMixin, models.Model):
|
||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||
verbose_name=_("ref"))
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
|
||||
|
|
|
@ -21,6 +21,7 @@ from taiga.base.fields import Field, MethodField
|
|||
from taiga.base.neighbors import NeighborsSerializerMixin
|
||||
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.due_dates.serializers import DueDateSerializerMixin
|
||||
from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin
|
||||
|
@ -33,7 +34,8 @@ from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin
|
|||
class IssueListSerializer(VoteResourceSerializerMixin, WatchedResourceSerializer,
|
||||
OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin,
|
||||
StatusExtraInfoSerializerMixin, ProjectExtraInfoSerializerMixin,
|
||||
TaggedInProjectResourceSerializer, serializers.LightSerializer):
|
||||
DueDateSerializerMixin, TaggedInProjectResourceSerializer,
|
||||
serializers.LightSerializer):
|
||||
id = Field()
|
||||
ref = Field()
|
||||
severity = Field(attr="severity_id")
|
||||
|
|
|
@ -82,7 +82,7 @@ def issues_to_csv(project, queryset):
|
|||
"sprint_estimated_finish", "owner", "owner_full_name", "assigned_to",
|
||||
"assigned_to_full_name", "status", "severity", "priority", "type",
|
||||
"is_closed", "attachments", "external_reference", "tags", "watchers",
|
||||
"voters", "created_date", "modified_date", "finished_date"]
|
||||
"voters", "created_date", "modified_date", "finished_date", "due_date"]
|
||||
|
||||
custom_attrs = project.issuecustomattributes.all()
|
||||
for custom_attr in custom_attrs:
|
||||
|
@ -125,6 +125,7 @@ def issues_to_csv(project, queryset):
|
|||
"created_date": issue.created_date,
|
||||
"modified_date": issue.modified_date,
|
||||
"finished_date": issue.finished_date,
|
||||
"due_date": issue.due_date,
|
||||
}
|
||||
|
||||
for custom_attr in custom_attrs:
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2018-04-09 09:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tasks', '0011_auto_20160928_0755'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='due_date',
|
||||
field=models.DateField(blank=True, default=None, null=True, verbose_name='due date'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='due_date_reason',
|
||||
field=models.TextField(blank=True, default='', verbose_name='reason for the due date'),
|
||||
),
|
||||
]
|
|
@ -24,13 +24,14 @@ from django.utils import timezone
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from taiga.base.utils.time import timestamp_ms
|
||||
from taiga.projects.due_dates.models import DueDateMixin
|
||||
from taiga.projects.occ import OCCModelMixin
|
||||
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||
from taiga.projects.mixins.blocked import BlockedMixin
|
||||
from taiga.projects.tagging.models import TaggedMixin
|
||||
|
||||
|
||||
class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
||||
class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateMixin, models.Model):
|
||||
user_story = models.ForeignKey("userstories.UserStory", null=True, blank=True,
|
||||
related_name="tasks", verbose_name=_("user story"))
|
||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||
|
|
|
@ -22,6 +22,7 @@ from taiga.base.neighbors import NeighborsSerializerMixin
|
|||
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.attachments.serializers import BasicAttachmentsInfoSerializerMixin
|
||||
from taiga.projects.due_dates.serializers import DueDateSerializerMixin
|
||||
from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin
|
||||
|
@ -36,7 +37,8 @@ class TaskListSerializer(VoteResourceSerializerMixin, WatchedResourceSerializer,
|
|||
OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin,
|
||||
StatusExtraInfoSerializerMixin, ProjectExtraInfoSerializerMixin,
|
||||
BasicAttachmentsInfoSerializerMixin, TaggedInProjectResourceSerializer,
|
||||
TotalCommentsSerializerMixin, serializers.LightSerializer):
|
||||
TotalCommentsSerializerMixin, DueDateSerializerMixin,
|
||||
serializers.LightSerializer):
|
||||
|
||||
id = Field()
|
||||
user_story = Field(attr="user_story_id")
|
||||
|
|
|
@ -122,7 +122,7 @@ def tasks_to_csv(project, queryset):
|
|||
"sprint_estimated_finish", "owner", "owner_full_name", "assigned_to",
|
||||
"assigned_to_full_name", "status", "is_iocaine", "is_closed", "us_order",
|
||||
"taskboard_order", "attachments", "external_reference", "tags", "watchers", "voters",
|
||||
"created_date", "modified_date", "finished_date"]
|
||||
"created_date", "modified_date", "finished_date", "due_date"]
|
||||
|
||||
custom_attrs = project.taskcustomattributes.all()
|
||||
for custom_attr in custom_attrs:
|
||||
|
@ -167,6 +167,7 @@ def tasks_to_csv(project, queryset):
|
|||
"created_date": task.created_date,
|
||||
"modified_date": task.modified_date,
|
||||
"finished_date": task.finished_date,
|
||||
"due_date": task.due_date,
|
||||
}
|
||||
for custom_attr in custom_attrs:
|
||||
value = task.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2018-04-09 09:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('userstories', '0014_auto_20160928_0540'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userstory',
|
||||
name='due_date',
|
||||
field=models.DateField(blank=True, default=None, null=True, verbose_name='due date'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userstory',
|
||||
name='due_date_reason',
|
||||
field=models.TextField(blank=True, default='', verbose_name='reason for the due date'),
|
||||
),
|
||||
]
|
|
@ -26,6 +26,7 @@ from django.utils import timezone
|
|||
from picklefield.fields import PickledObjectField
|
||||
|
||||
from taiga.base.utils.time import timestamp_ms
|
||||
from taiga.projects.due_dates.models import DueDateMixin
|
||||
from taiga.projects.tagging.models import TaggedMixin
|
||||
from taiga.projects.occ import OCCModelMixin
|
||||
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||
|
@ -57,7 +58,7 @@ class RolePoints(models.Model):
|
|||
return self.user_story.project
|
||||
|
||||
|
||||
class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
||||
class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateMixin, models.Model):
|
||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||
verbose_name=_("ref"))
|
||||
milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
|
||||
|
|
|
@ -22,6 +22,7 @@ from taiga.base.neighbors import NeighborsSerializerMixin
|
|||
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.attachments.serializers import BasicAttachmentsInfoSerializerMixin
|
||||
from taiga.projects.due_dates.serializers import DueDateSerializerMixin
|
||||
from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin
|
||||
from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin
|
||||
|
@ -49,7 +50,7 @@ class UserStoryListSerializer(ProjectExtraInfoSerializerMixin,
|
|||
OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin,
|
||||
StatusExtraInfoSerializerMixin, BasicAttachmentsInfoSerializerMixin,
|
||||
TaggedInProjectResourceSerializer, TotalCommentsSerializerMixin,
|
||||
serializers.LightSerializer):
|
||||
DueDateSerializerMixin, serializers.LightSerializer):
|
||||
|
||||
id = Field()
|
||||
ref = Field()
|
||||
|
|
|
@ -197,7 +197,7 @@ def userstories_to_csv(project, queryset):
|
|||
"created_date", "modified_date", "finish_date",
|
||||
"client_requirement", "team_requirement", "attachments",
|
||||
"generated_from_issue", "external_reference", "tasks",
|
||||
"tags", "watchers", "voters"]
|
||||
"tags", "watchers", "voters", "due_date"]
|
||||
|
||||
custom_attrs = project.userstorycustomattributes.all()
|
||||
for custom_attr in custom_attrs:
|
||||
|
@ -249,7 +249,8 @@ def userstories_to_csv(project, queryset):
|
|||
"tasks": ",".join([str(task.ref) for task in us.tasks.all()]),
|
||||
"tags": ",".join(us.tags or []),
|
||||
"watchers": us.watchers,
|
||||
"voters": us.total_voters
|
||||
"voters": us.total_voters,
|
||||
"due_date": us.due_date,
|
||||
}
|
||||
|
||||
us_role_points_by_role_id = {us_rp.role.id: us_rp.points.value for us_rp in us.role_points.all()}
|
||||
|
|
|
@ -591,9 +591,9 @@ def test_custom_fields_csv_generation():
|
|||
data.seek(0)
|
||||
reader = csv.reader(data)
|
||||
row = next(reader)
|
||||
assert row[23] == attr.name
|
||||
assert row[24] == attr.name
|
||||
row = next(reader)
|
||||
assert row[23] == "val1"
|
||||
assert row[24] == "val1"
|
||||
|
||||
|
||||
def test_api_validator_assigned_to_when_update_issues(client):
|
||||
|
|
|
@ -574,9 +574,9 @@ def test_custom_fields_csv_generation():
|
|||
data.seek(0)
|
||||
reader = csv.reader(data)
|
||||
row = next(reader)
|
||||
assert row[24] == attr.name
|
||||
assert row[25] == attr.name
|
||||
row = next(reader)
|
||||
assert row[24] == "val1"
|
||||
assert row[25] == "val1"
|
||||
|
||||
|
||||
def test_get_tasks_including_attachments(client):
|
||||
|
|
|
@ -899,9 +899,9 @@ def test_custom_fields_csv_generation():
|
|||
data.seek(0)
|
||||
reader = csv.reader(data)
|
||||
row = next(reader)
|
||||
assert row[28] == attr.name
|
||||
assert row[29] == attr.name
|
||||
row = next(reader)
|
||||
assert row[28] == "val1"
|
||||
assert row[29] == "val1"
|
||||
|
||||
|
||||
def test_update_userstory_respecting_watchers(client):
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import datetime as dt
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from taiga.projects.due_dates.serializers import DueDateSerializerMixin
|
||||
|
||||
@pytest.mark.parametrize('due_date, is_closed, expected', [
|
||||
(None, False, 'not_set'),
|
||||
(dt.date(2100, 1, 1), True, 'no_longer_applicable'),
|
||||
(dt.date(2100, 12, 31), False, 'set'),
|
||||
(dt.date(2000, 1, 1), False, 'past_due'),
|
||||
(timezone.now().date(), False, 'due_soon'),
|
||||
])
|
||||
def test_due_date_status(due_date, is_closed, expected):
|
||||
serializer = DueDateSerializerMixin()
|
||||
obj_status = mock.MagicMock(is_closed=is_closed)
|
||||
obj = mock.MagicMock(due_date=due_date, status=obj_status)
|
||||
status = serializer.get_due_date_status(obj)
|
||||
assert status == expected
|
Loading…
Reference in New Issue