Add csv for Epics

remotes/origin/issue/4795/notification_even_they_are_disabled
David Barragán Merino 2016-07-20 20:47:54 +02:00
parent 79eeccf379
commit 0a5ba7b3ae
11 changed files with 241 additions and 122 deletions

View File

@ -33,6 +33,9 @@ urls = {
"project": "/project/{0}", # project.slug "project": "/project/{0}", # project.slug
"epics": "/project/{0}/epics/", # project.slug
"epic": "/project/{0}/epic/{1}", # project.slug, epic.ref
"backlog": "/project/{0}/backlog/", # project.slug "backlog": "/project/{0}/backlog/", # project.slug
"taskboard": "/project/{0}/taskboard/{1}", # project.slug, milestone.slug "taskboard": "/project/{0}/taskboard/{1}", # project.slug, milestone.slug
"kanban": "/project/{0}/kanban/", # project.slug "kanban": "/project/{0}/kanban/", # project.slug

View File

@ -243,6 +243,20 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
services.remove_user_from_project(request.user, project) services.remove_user_from_project(request.user, project)
return response.Ok() return response.Ok()
def _regenerate_csv_uuid(self, project, field):
uuid_value = uuid.uuid4().hex
setattr(project, field, uuid_value)
project.save()
return uuid_value
@detail_route(methods=["POST"])
def regenerate_epics_csv_uuid(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "regenerate_epics_csv_uuid", project)
self.pre_conditions_on_save(project)
data = {"uuid": self._regenerate_csv_uuid(project, "epics_csv_uuid")}
return response.Ok(data)
@detail_route(methods=["POST"]) @detail_route(methods=["POST"])
def regenerate_userstories_csv_uuid(self, request, pk=None): def regenerate_userstories_csv_uuid(self, request, pk=None):
project = self.get_object() project = self.get_object()
@ -251,14 +265,6 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
data = {"uuid": self._regenerate_csv_uuid(project, "userstories_csv_uuid")} data = {"uuid": self._regenerate_csv_uuid(project, "userstories_csv_uuid")}
return response.Ok(data) return response.Ok(data)
@detail_route(methods=["POST"])
def regenerate_issues_csv_uuid(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "regenerate_issues_csv_uuid", project)
self.pre_conditions_on_save(project)
data = {"uuid": self._regenerate_csv_uuid(project, "issues_csv_uuid")}
return response.Ok(data)
@detail_route(methods=["POST"]) @detail_route(methods=["POST"])
def regenerate_tasks_csv_uuid(self, request, pk=None): def regenerate_tasks_csv_uuid(self, request, pk=None):
project = self.get_object() project = self.get_object()
@ -267,6 +273,14 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
data = {"uuid": self._regenerate_csv_uuid(project, "tasks_csv_uuid")} data = {"uuid": self._regenerate_csv_uuid(project, "tasks_csv_uuid")}
return response.Ok(data) return response.Ok(data)
@detail_route(methods=["POST"])
def regenerate_issues_csv_uuid(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "regenerate_issues_csv_uuid", project)
self.pre_conditions_on_save(project)
data = {"uuid": self._regenerate_csv_uuid(project, "issues_csv_uuid")}
return response.Ok(data)
@list_route(methods=["GET"]) @list_route(methods=["GET"])
def by_slug(self, request, *args, **kwargs): def by_slug(self, request, *args, **kwargs):
slug = request.QUERY_PARAMS.get("slug", None) slug = request.QUERY_PARAMS.get("slug", None)
@ -293,12 +307,6 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
self.check_permissions(request, "stats", project) self.check_permissions(request, "stats", project)
return response.Ok(services.get_stats_for_project(project)) return response.Ok(services.get_stats_for_project(project))
def _regenerate_csv_uuid(self, project, field):
uuid_value = uuid.uuid4().hex
setattr(project, field, uuid_value)
project.save()
return uuid_value
@detail_route(methods=["GET"]) @detail_route(methods=["GET"])
def member_stats(self, request, pk=None): def member_stats(self, request, pk=None):
project = self.get_object() project = self.get_object()

View File

@ -154,18 +154,18 @@ class EpicViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin,
return self.retrieve(request, **retrieve_kwargs) return self.retrieve(request, **retrieve_kwargs)
#@list_route(methods=["GET"]) @list_route(methods=["GET"])
#def csv(self, request): def csv(self, request):
# uuid = request.QUERY_PARAMS.get("uuid", None) uuid = request.QUERY_PARAMS.get("uuid", None)
# if uuid is None: if uuid is None:
# return response.NotFound() return response.NotFound()
# project = get_object_or_404(Project, epics_csv_uuid=uuid) project = get_object_or_404(Project, epics_csv_uuid=uuid)
# queryset = project.epics.all().order_by('ref') queryset = project.epics.all().order_by('ref')
# data = services.epics_to_csv(project, queryset) data = services.epics_to_csv(project, queryset)
# csv_response = HttpResponse(data.getvalue(), content_type='application/csv; charset=utf-8') csv_response = HttpResponse(data.getvalue(), content_type='application/csv; charset=utf-8')
# csv_response['Content-Disposition'] = 'attachment; filename="epics.csv"' csv_response['Content-Disposition'] = 'attachment; filename="epics.csv"'
# return csv_response return csv_response
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):

View File

@ -105,66 +105,57 @@ def snapshot_epics_in_bulk(bulk_data, user):
##################################################### #####################################################
# CSV # CSV
##################################################### #####################################################
#
#def epics_to_csv(project, queryset): def epics_to_csv(project, queryset):
# csv_data = io.StringIO() csv_data = io.StringIO()
# fieldnames = ["ref", "subject", "description", "user_story", "sprint", "sprint_estimated_start", fieldnames = ["ref", "subject", "description", "owner", "owner_full_name", "assigned_to",
# "sprint_estimated_finish", "owner", "owner_full_name", "assigned_to", "assigned_to_full_name", "status", "epics_order", "client_requirement",
# "assigned_to_full_name", "status", "is_iocaine", "is_closed", "us_order", "team_requirement", "attachments", "tags", "watchers", "voters",
# "epicboard_order", "attachments", "external_reference", "tags", "watchers", "voters", "created_date", "modified_date"]
# "created_date", "modified_date", "finished_date"]
# custom_attrs = project.epiccustomattributes.all()
# custom_attrs = project.epiccustomattributes.all() for custom_attr in custom_attrs:
# for custom_attr in custom_attrs: fieldnames.append(custom_attr.name)
# fieldnames.append(custom_attr.name)
# queryset = queryset.prefetch_related("attachments",
# queryset = queryset.prefetch_related("attachments", "custom_attributes_values")
# "custom_attributes_values") queryset = queryset.select_related("owner",
# queryset = queryset.select_related("milestone", "assigned_to",
# "owner", "status",
# "assigned_to", "project")
# "status",
# "project") queryset = attach_total_voters_to_queryset(queryset)
# queryset = attach_watchers_to_queryset(queryset)
# queryset = attach_total_voters_to_queryset(queryset)
# queryset = attach_watchers_to_queryset(queryset) writer = csv.DictWriter(csv_data, fieldnames=fieldnames)
# writer.writeheader()
# writer = csv.DictWriter(csv_data, fieldnames=fieldnames) for epic in queryset:
# writer.writeheader() epic_data = {
# for epic in queryset: "ref": epic.ref,
# epic_data = { "subject": epic.subject,
# "ref": epic.ref, "description": epic.description,
# "subject": epic.subject, "owner": epic.owner.username if epic.owner else None,
# "description": epic.description, "owner_full_name": epic.owner.get_full_name() if epic.owner else None,
# "user_story": epic.user_story.ref if epic.user_story else None, "assigned_to": epic.assigned_to.username if epic.assigned_to else None,
# "sprint": epic.milestone.name if epic.milestone else None, "assigned_to_full_name": epic.assigned_to.get_full_name() if epic.assigned_to else None,
# "sprint_estimated_start": epic.milestone.estimated_start if epic.milestone else None, "status": epic.status.name if epic.status else None,
# "sprint_estimated_finish": epic.milestone.estimated_finish if epic.milestone else None, "epics_order": epic.epics_order,
# "owner": epic.owner.username if epic.owner else None, "client_requirement": epic.client_requirement,
# "owner_full_name": epic.owner.get_full_name() if epic.owner else None, "team_requirement": epic.team_requirement,
# "assigned_to": epic.assigned_to.username if epic.assigned_to else None, "attachments": epic.attachments.count(),
# "assigned_to_full_name": epic.assigned_to.get_full_name() if epic.assigned_to else None, "tags": ",".join(epic.tags or []),
# "status": epic.status.name if epic.status else None, "watchers": epic.watchers,
# "is_iocaine": epic.is_iocaine, "voters": epic.total_voters,
# "is_closed": epic.status is not None and epic.status.is_closed, "created_date": epic.created_date,
# "us_order": epic.us_order, "modified_date": epic.modified_date,
# "epicboard_order": epic.epicboard_order, }
# "attachments": epic.attachments.count(), for custom_attr in custom_attrs:
# "external_reference": epic.external_reference, value = epic.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
# "tags": ",".join(epic.tags or []), epic_data[custom_attr.name] = value
# "watchers": epic.watchers,
# "voters": epic.total_voters, writer.writerow(epic_data)
# "created_date": epic.created_date,
# "modified_date": epic.modified_date, return csv_data
# "finished_date": epic.finished_date,
# }
# for custom_attr in custom_attrs:
# value = epic.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
# epic_data[custom_attr.name] = value
#
# writer.writerow(epic_data)
#
# return csv_data
##################################################### #####################################################

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-07-20 17:57
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('projects', '0049_auto_20160629_1443'),
]
operations = [
migrations.AddField(
model_name='project',
name='epics_csv_uuid',
field=models.CharField(blank=True, db_index=True, default=None, editable=False, max_length=32, null=True),
),
]

View File

@ -202,6 +202,8 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model):
looking_for_people_note = models.TextField(default="", null=False, blank=True, looking_for_people_note = models.TextField(default="", null=False, blank=True,
verbose_name=_("loking for people note")) verbose_name=_("loking for people note"))
epics_csv_uuid = models.CharField(max_length=32, editable=False, null=True,
blank=True, default=None, db_index=True)
userstories_csv_uuid = models.CharField(max_length=32, editable=False, userstories_csv_uuid = models.CharField(max_length=32, editable=False,
null=True, blank=True, null=True, blank=True,
default=None, db_index=True) default=None, db_index=True)

View File

@ -62,6 +62,7 @@ class ProjectPermission(TaigaResourcePermission):
stats_perms = HasProjectPerm('view_project') stats_perms = HasProjectPerm('view_project')
member_stats_perms = HasProjectPerm('view_project') member_stats_perms = HasProjectPerm('view_project')
issues_stats_perms = HasProjectPerm('view_project') issues_stats_perms = HasProjectPerm('view_project')
regenerate_epics_csv_uuid_perms = IsProjectAdmin()
regenerate_userstories_csv_uuid_perms = IsProjectAdmin() regenerate_userstories_csv_uuid_perms = IsProjectAdmin()
regenerate_issues_csv_uuid_perms = IsProjectAdmin() regenerate_issues_csv_uuid_perms = IsProjectAdmin()
regenerate_tasks_csv_uuid_perms = IsProjectAdmin() regenerate_tasks_csv_uuid_perms = IsProjectAdmin()

View File

@ -373,9 +373,10 @@ class ProjectDetailSerializer(ProjectSerializer):
# Admin fields # Admin fields
is_private_extra_info = MethodField() is_private_extra_info = MethodField()
max_memberships = MethodField() max_memberships = MethodField()
issues_csv_uuid = Field() epics_csv_uuid = Field()
tasks_csv_uuid = Field()
userstories_csv_uuid = Field() userstories_csv_uuid = Field()
tasks_csv_uuid = Field()
issues_csv_uuid = Field()
transfer_token = Field() transfer_token = Field()
milestones = MethodField() milestones = MethodField()
@ -404,8 +405,8 @@ class ProjectDetailSerializer(ProjectSerializer):
ret = super().to_value(instance) ret = super().to_value(instance)
admin_fields = [ admin_fields = [
"is_private_extra_info", "max_memberships", "issues_csv_uuid", "epics_csv_uuid", "userstories_csv_uuid", "tasks_csv_uuid", "issues_csv_uuid",
"tasks_csv_uuid", "userstories_csv_uuid", "transfer_token" "is_private_extra_info", "max_memberships", "transfer_token",
] ]
is_admin_user = False is_admin_user = False

View File

@ -59,29 +59,29 @@ def data():
m.public_project = f.ProjectFactory(is_private=False, m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)), anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
#epics_csv_uuid=uuid.uuid4().hex) epics_csv_uuid=uuid.uuid4().hex)
m.public_project = attach_project_extra_info(Project.objects.all()).get(id=m.public_project.id) m.public_project = attach_project_extra_info(Project.objects.all()).get(id=m.public_project.id)
m.private_project1 = f.ProjectFactory(is_private=True, m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)), anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
#epics_csv_uuid=uuid.uuid4().hex) epics_csv_uuid=uuid.uuid4().hex)
m.private_project1 = attach_project_extra_info(Project.objects.all()).get(id=m.private_project1.id) m.private_project1 = attach_project_extra_info(Project.objects.all()).get(id=m.private_project1.id)
m.private_project2 = f.ProjectFactory(is_private=True, m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[], anon_permissions=[],
public_permissions=[], public_permissions=[],
owner=m.project_owner) owner=m.project_owner,
#epics_csv_uuid=uuid.uuid4().hex) epics_csv_uuid=uuid.uuid4().hex)
m.private_project2 = attach_project_extra_info(Project.objects.all()).get(id=m.private_project2.id) m.private_project2 = attach_project_extra_info(Project.objects.all()).get(id=m.private_project2.id)
m.blocked_project = f.ProjectFactory(is_private=True, m.blocked_project = f.ProjectFactory(is_private=True,
anon_permissions=[], anon_permissions=[],
public_permissions=[], public_permissions=[],
owner=m.project_owner, owner=m.project_owner,
#epics_csv_uuid=uuid.uuid4().hex, epics_csv_uuid=uuid.uuid4().hex,
blocked_code=project_choices.BLOCKED_BY_STAFF) blocked_code=project_choices.BLOCKED_BY_STAFF)
m.blocked_project = attach_project_extra_info(Project.objects.all()).get(id=m.blocked_project.id) m.blocked_project = attach_project_extra_info(Project.objects.all()).get(id=m.blocked_project.id)
@ -873,29 +873,29 @@ def test_epic_watchers_retrieve(client, data):
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]
#def test_epics_csv(client, data): def test_epics_csv(client, data):
# url = reverse('epics-csv') url = reverse('epics-csv')
# csv_public_uuid = data.public_project.epics_csv_uuid csv_public_uuid = data.public_project.epics_csv_uuid
# csv_private1_uuid = data.private_project1.epics_csv_uuid csv_private1_uuid = data.private_project1.epics_csv_uuid
# csv_private2_uuid = data.private_project1.epics_csv_uuid csv_private2_uuid = data.private_project1.epics_csv_uuid
# csv_blocked_uuid = data.blocked_project.epics_csv_uuid csv_blocked_uuid = data.blocked_project.epics_csv_uuid
#
# users = [ users = [
# None, None,
# data.registered_user, data.registered_user,
# data.project_member_without_perms, data.project_member_without_perms,
# data.project_member_with_perms, data.project_member_with_perms,
# data.project_owner data.project_owner
# ] ]
#
# results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_public_uuid), None, users) results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_public_uuid), None, users)
# assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
#
# results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private1_uuid), None, users) results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private1_uuid), None, users)
# assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
#
# results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private2_uuid), None, users) results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private2_uuid), None, users)
# assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
#
# results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_blocked_uuid), None, users) results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_blocked_uuid), None, users)
# assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]

View File

@ -486,6 +486,31 @@ def test_invitations_retrieve(client, data):
assert results == [200, 200, 200, 200] assert results == [200, 200, 200, 200]
def test_regenerate_epics_csv_uuid(client, data):
public_url = reverse('projects-regenerate-epics-csv-uuid', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-regenerate-epics-csv-uuid', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-regenerate-epics-csv-uuid', kwargs={"pk": data.private_project2.pk})
blocked_url = reverse('projects-regenerate-epics-csv-uuid', kwargs={"pk": data.blocked_project.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, None, users)
assert results == [401, 403, 403, 200]
results = helper_test_http_method(client, 'post', private1_url, None, users)
assert results == [401, 403, 403, 200]
results = helper_test_http_method(client, 'post', private2_url, None, users)
assert results == [404, 404, 403, 200]
results = helper_test_http_method(client, 'post', blocked_url, None, users)
assert results == [404, 404, 403, 451]
def test_regenerate_userstories_csv_uuid(client, data): def test_regenerate_userstories_csv_uuid(client, data):
public_url = reverse('projects-regenerate-userstories-csv-uuid', kwargs={"pk": data.public_project.pk}) public_url = reverse('projects-regenerate-userstories-csv-uuid', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-regenerate-userstories-csv-uuid', kwargs={"pk": data.private_project1.pk}) private1_url = reverse('projects-regenerate-userstories-csv-uuid', kwargs={"pk": data.private_project1.pk})

View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# Copyright (C) 2014-2016 Anler Hernández <hello@anler.me>
# 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 uuid
import csv
from unittest import mock
from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.projects.epics import services
from .. import factories as f
import pytest
pytestmark = pytest.mark.django_db
def test_get_invalid_csv(client):
url = reverse("epics-csv")
response = client.get(url)
assert response.status_code == 404
response = client.get("{}?uuid={}".format(url, "not-valid-uuid"))
assert response.status_code == 404
def test_get_valid_csv(client):
url = reverse("epics-csv")
project = f.ProjectFactory.create(epics_csv_uuid=uuid.uuid4().hex)
response = client.get("{}?uuid={}".format(url, project.epics_csv_uuid))
assert response.status_code == 200
def test_custom_fields_csv_generation():
project = f.ProjectFactory.create(epics_csv_uuid=uuid.uuid4().hex)
attr = f.EpicCustomAttributeFactory.create(project=project, name="attr1", description="desc")
epic = f.EpicFactory.create(project=project)
attr_values = epic.custom_attributes_values
attr_values.attributes_values = {str(attr.id):"val1"}
attr_values.save()
queryset = project.epics.all()
data = services.epics_to_csv(project, queryset)
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[17] == attr.name
row = next(reader)
assert row[17] == "val1"