Added due_date to taks, us and issues

remotes/origin/3.4.0rc
Miguel Gonzalez 2018-02-13 09:23:45 +01:00 committed by Alex Hermida
parent 8b76b5787b
commit c2149ef6a5
20 changed files with 197 additions and 16 deletions

View File

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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,

View File

@ -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")

View File

@ -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:

View File

@ -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'),
),
]

View File

@ -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,

View File

@ -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")

View File

@ -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)

View File

@ -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'),
),
]

View File

@ -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,

View File

@ -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()

View File

@ -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()}

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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