Merge pull request #263 from taigaio/us/1913/csv-reports

US#1913: CSV Reports
remotes/origin/enhancement/email-actions
David Barragán Merino 2015-03-10 19:11:22 +01:00
commit 6e780bb720
22 changed files with 570 additions and 15 deletions

View File

@ -5,6 +5,7 @@
### Features ### Features
- Added custom fields per project for user stories, tasks and issues. - Added custom fields per project for user stories, tasks and issues.
- Support of export to CSV user stories, tasks and issues.
- Allow public projects. - Allow public projects.
### Misc ### Misc

View File

@ -53,6 +53,7 @@ from .votes.utils import attach_votescount_to_queryset
class ProjectViewSet(ModelCrudViewSet): class ProjectViewSet(ModelCrudViewSet):
serializer_class = serializers.ProjectDetailSerializer serializer_class = serializers.ProjectDetailSerializer
admin_serializer_class = serializers.ProjectDetailAdminSerializer
list_serializer_class = serializers.ProjectSerializer list_serializer_class = serializers.ProjectSerializer
permission_classes = (permissions.ProjectPermission, ) permission_classes = (permissions.ProjectPermission, )
filter_backends = (filters.CanViewProjectObjFilterBackend,) filter_backends = (filters.CanViewProjectObjFilterBackend,)
@ -61,6 +62,23 @@ class ProjectViewSet(ModelCrudViewSet):
qs = models.Project.objects.all() qs = models.Project.objects.all()
return attach_votescount_to_queryset(qs, as_field="stars_count") return attach_votescount_to_queryset(qs, as_field="stars_count")
def get_serializer_class(self):
if self.action == "list":
return self.list_serializer_class
elif self.action == "create":
return self.serializer_class
if self.action == "by_slug":
slug = self.request.QUERY_PARAMS.get("slug", None)
project = get_object_or_404(models.Project, slug=slug)
else:
project = self.get_object()
if permissions_service.is_project_owner(self.request.user, project):
return self.admin_serializer_class
return self.serializer_class
@list_route(methods=["GET"]) @list_route(methods=["GET"])
def by_slug(self, request): def by_slug(self, request):
slug = request.QUERY_PARAMS.get("slug", None) slug = request.QUERY_PARAMS.get("slug", None)
@ -87,6 +105,33 @@ class ProjectViewSet(ModelCrudViewSet):
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=["POST"])
def regenerate_userstories_csv_uuid(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "regenerate_userstories_csv_uuid", project)
data = {"uuid": self._regenerate_csv_uuid(project, "userstories_csv_uuid")}
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)
data = {"uuid": self._regenerate_csv_uuid(project, "issues_csv_uuid")}
return response.Ok(data)
@detail_route(methods=["POST"])
def regenerate_tasks_csv_uuid(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "regenerate_tasks_csv_uuid", project)
data = {"uuid": self._regenerate_csv_uuid(project, "tasks_csv_uuid")}
return response.Ok(data)
@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

@ -16,7 +16,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
from django.http import Http404 from django.http import Http404, HttpResponse
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
@ -24,7 +24,6 @@ from taiga.base import response
from taiga.base.decorators import detail_route, list_route from taiga.base.decorators import detail_route, list_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404 from taiga.base.api.utils import get_object_or_404
from taiga.base import tags
from taiga.users.models import User from taiga.users.models import User
@ -163,6 +162,19 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
issue = get_object_or_404(models.Issue, ref=ref, project_id=project_id) issue = get_object_or_404(models.Issue, ref=ref, project_id=project_id)
return self.retrieve(request, pk=issue.pk) return self.retrieve(request, pk=issue.pk)
@list_route(methods=["GET"])
def csv(self, request):
uuid = request.QUERY_PARAMS.get("uuid", None)
if uuid is None:
return response.NotFound()
project = get_object_or_404(Project, issues_csv_uuid=uuid)
queryset = project.issues.all().order_by('ref')
data = services.issues_to_csv(project, queryset)
csv_response = HttpResponse(data.getvalue(), content_type='application/csv')
csv_response['Content-Disposition'] = 'attachment; filename="issues.csv"'
return csv_response
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):
serializer = serializers.IssuesBulkSerializer(data=request.DATA) serializer = serializers.IssuesBulkSerializer(data=request.DATA)

View File

@ -28,6 +28,7 @@ class IssuePermission(TaigaResourcePermission):
update_perms = HasProjectPerm('modify_issue') update_perms = HasProjectPerm('modify_issue')
destroy_perms = HasProjectPerm('delete_issue') destroy_perms = HasProjectPerm('delete_issue')
list_perms = AllowAny() list_perms = AllowAny()
csv_perms = AllowAny()
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues') upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues') downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
bulk_create_perms = HasProjectPerm('add_issue') bulk_create_perms = HasProjectPerm('add_issue')

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import csv
from taiga.base.utils import db, text from taiga.base.utils import db, text
from . import models from . import models
@ -58,3 +61,42 @@ def update_issues_order_in_bulk(bulk_data):
issue_ids.append(issue_id) issue_ids.append(issue_id)
new_order_values.append({"order": new_order_value}) new_order_values.append({"order": new_order_value})
db.update_in_bulk_with_ids(issue_ids, new_order_values, model=models.Issue) db.update_in_bulk_with_ids(issue_ids, new_order_values, model=models.Issue)
def issues_to_csv(project, queryset):
csv_data = io.StringIO()
fieldnames = ["ref", "subject", "description", "milestone", "owner",
"owner_full_name", "assigned_to", "assigned_to_full_name",
"status", "severity", "priority", "type", "is_closed",
"attachments", "external_reference"]
for custom_attr in project.issuecustomattributes.all():
fieldnames.append(custom_attr.name)
writer = csv.DictWriter(csv_data, fieldnames=fieldnames)
writer.writeheader()
for issue in queryset:
issue_data = {
"ref": issue.ref,
"subject": issue.subject,
"description": issue.description,
"milestone": issue.milestone.name if issue.milestone else None,
"owner": issue.owner.username,
"owner_full_name": issue.owner.get_full_name(),
"assigned_to": issue.assigned_to.username if issue.assigned_to else None,
"assigned_to_full_name": issue.assigned_to.get_full_name() if issue.assigned_to else None,
"status": issue.status.name,
"severity": issue.severity.name,
"priority": issue.priority.name,
"type": issue.type.name,
"is_closed": issue.is_closed,
"attachments": issue.attachments.count(),
"external_reference": issue.external_reference,
}
for custom_attr in project.issuecustomattributes.all():
value = issue.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
issue_data[custom_attr.name] = value
writer.writerow(issue_data)
return csv_data

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('projects', '0017_fix_is_private_for_projects'),
]
operations = [
migrations.AddField(
model_name='project',
name='issues_csv_uuid',
field=models.CharField(editable=False, max_length=32, default=None, null=True, db_index=True, blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='project',
name='tasks_csv_uuid',
field=models.CharField(editable=False, max_length=32, default=None, null=True, db_index=True, blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='project',
name='userstories_csv_uuid',
field=models.CharField(editable=False, max_length=32, default=None, null=True, db_index=True, blank=True),
preserve_default=True,
),
]

View File

@ -15,6 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import itertools import itertools
import uuid
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
@ -163,6 +165,15 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
is_private = models.BooleanField(default=True, null=False, blank=True, is_private = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("is private")) verbose_name=_("is private"))
userstories_csv_uuid = models.CharField(max_length=32, editable=False,
null=True, blank=True,
default=None, db_index=True)
tasks_csv_uuid = models.CharField(max_length=32, editable=False, null=True,
blank=True, default=None, db_index=True)
issues_csv_uuid = models.CharField(max_length=32, editable=False,
null=True, blank=True, default=None,
db_index=True)
tags_colors = TextArrayField(dimension=2, null=False, blank=True, verbose_name=_("tags colors"), default=[]) tags_colors = TextArrayField(dimension=2, null=False, blank=True, verbose_name=_("tags colors"), default=[])
_importing = None _importing = None

View File

@ -54,6 +54,9 @@ class ProjectPermission(TaigaResourcePermission):
list_perms = AllowAny() list_perms = AllowAny()
stats_perms = HasProjectPerm('view_project') stats_perms = HasProjectPerm('view_project')
member_stats_perms = HasProjectPerm('view_project') member_stats_perms = HasProjectPerm('view_project')
regenerate_userstories_csv_uuid_perms = IsProjectOwner()
regenerate_issues_csv_uuid_perms = IsProjectOwner()
regenerate_tasks_csv_uuid_perms = IsProjectOwner()
star_perms = IsAuthenticated() star_perms = IsAuthenticated()
unstar_perms = IsAuthenticated() unstar_perms = IsAuthenticated()
issues_stats_perms = HasProjectPerm('view_project') issues_stats_perms = HasProjectPerm('view_project')

View File

@ -269,7 +269,8 @@ class ProjectSerializer(ModelSerializer):
class Meta: class Meta:
model = models.Project model = models.Project
read_only_fields = ("created_date", "modified_date", "owner") read_only_fields = ("created_date", "modified_date", "owner")
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref") exclude = ("last_us_ref", "last_task_ref", "last_issue_ref",
"issues_csv_uuid", "tasks_csv_uuid", "userstories_csv_uuid")
def get_stars_number(self, obj): def get_stars_number(self, obj):
# The "stars_count" attribute is attached in the get_queryset of the viewset. # The "stars_count" attribute is attached in the get_queryset of the viewset.
@ -301,6 +302,7 @@ class ProjectSerializer(ModelSerializer):
raise serializers.ValidationError("Total milestones must be major or equal to zero") raise serializers.ValidationError("Total milestones must be major or equal to zero")
return attrs return attrs
class ProjectDetailSerializer(ProjectSerializer): class ProjectDetailSerializer(ProjectSerializer):
roles = serializers.SerializerMethodField("get_roles") roles = serializers.SerializerMethodField("get_roles")
memberships = serializers.SerializerMethodField("get_memberships") memberships = serializers.SerializerMethodField("get_memberships")
@ -331,6 +333,13 @@ class ProjectDetailSerializer(ProjectSerializer):
return serializer.data return serializer.data
class ProjectDetailAdminSerializer(ProjectDetailSerializer):
class Meta:
model = models.Project
read_only_fields = ("created_date", "modified_date", "owner")
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
###################################################### ######################################################
## Starred ## Starred
###################################################### ######################################################

View File

@ -22,6 +22,7 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.projects.models import Project from taiga.projects.models import Project
from django.http import HttpResponse
from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin
@ -71,6 +72,19 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
task = get_object_or_404(models.Task, ref=ref, project_id=project_id) task = get_object_or_404(models.Task, ref=ref, project_id=project_id)
return self.retrieve(request, pk=task.pk) return self.retrieve(request, pk=task.pk)
@list_route(methods=["GET"])
def csv(self, request):
uuid = request.QUERY_PARAMS.get("uuid", None)
if uuid is None:
return response.NotFound()
project = get_object_or_404(Project, tasks_csv_uuid=uuid)
queryset = project.tasks.all().order_by('ref')
data = services.tasks_to_csv(project, queryset)
csv_response = HttpResponse(data.getvalue(), content_type='application/csv')
csv_response['Content-Disposition'] = 'attachment; filename="tasks.csv"'
return csv_response
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):
serializer = serializers.TasksBulkSerializer(data=request.DATA) serializer = serializers.TasksBulkSerializer(data=request.DATA)

View File

@ -26,5 +26,6 @@ class TaskPermission(TaigaResourcePermission):
update_perms = HasProjectPerm('modify_task') update_perms = HasProjectPerm('modify_task')
destroy_perms = HasProjectPerm('delete_task') destroy_perms = HasProjectPerm('delete_task')
list_perms = AllowAny() list_perms = AllowAny()
csv_perms = AllowAny()
bulk_create_perms = HasProjectPerm('add_task') bulk_create_perms = HasProjectPerm('add_task')
bulk_update_order_perms = HasProjectPerm('modify_task') bulk_update_order_perms = HasProjectPerm('modify_task')

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import csv
from taiga.base.utils import db, text from taiga.base.utils import db, text
from taiga.projects.history.services import take_snapshot from taiga.projects.history.services import take_snapshot
from taiga.events import events from taiga.events import events
@ -75,3 +78,42 @@ def snapshot_tasks_in_bulk(bulk_data, user):
take_snapshot(task, user=user) take_snapshot(task, user=user)
except models.UserStory.DoesNotExist: except models.UserStory.DoesNotExist:
pass pass
def tasks_to_csv(project, queryset):
csv_data = io.StringIO()
fieldnames = ["ref", "subject", "description", "user_story", "milestone", "owner",
"owner_full_name", "assigned_to", "assigned_to_full_name",
"status", "is_iocaine", "is_closed", "us_order",
"taskboard_order", "attachments", "external_reference"]
for custom_attr in project.taskcustomattributes.all():
fieldnames.append(custom_attr.name)
writer = csv.DictWriter(csv_data, fieldnames=fieldnames)
writer.writeheader()
for task in queryset:
task_data = {
"ref": task.ref,
"subject": task.subject,
"description": task.description,
"user_story": task.user_story.ref if task.user_story else None,
"milestone": task.milestone.name if task.milestone else None,
"owner": task.owner.username,
"owner_full_name": task.owner.get_full_name(),
"assigned_to": task.assigned_to.username if task.assigned_to else None,
"assigned_to_full_name": task.assigned_to.get_full_name() if task.assigned_to else None,
"status": task.status.name,
"is_iocaine": task.is_iocaine,
"is_closed": task.status.is_closed,
"us_order": task.us_order,
"taskboard_order": task.taskboard_order,
"attachments": task.attachments.count(),
"external_reference": task.external_reference,
}
for custom_attr in project.taskcustomattributes.all():
value = task.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
task_data[custom_attr.name] = value
writer.writerow(task_data)
return csv_data

View File

@ -22,6 +22,7 @@ from django.apps import apps
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
@ -102,6 +103,19 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
userstory = get_object_or_404(models.UserStory, ref=ref, project_id=project_id) userstory = get_object_or_404(models.UserStory, ref=ref, project_id=project_id)
return self.retrieve(request, pk=userstory.pk) return self.retrieve(request, pk=userstory.pk)
@list_route(methods=["GET"])
def csv(self, request):
uuid = request.QUERY_PARAMS.get("uuid", None)
if uuid is None:
return response.NotFound()
project = get_object_or_404(Project, userstories_csv_uuid=uuid)
queryset = project.user_stories.all().order_by('ref')
data = services.userstories_to_csv(project, queryset)
csv_response = HttpResponse(data.getvalue(), content_type='application/csv')
csv_response['Content-Disposition'] = 'attachment; filename="userstories.csv"'
return csv_response
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):
serializer = serializers.UserStoriesBulkSerializer(data=request.DATA) serializer = serializers.UserStoriesBulkSerializer(data=request.DATA)

View File

@ -25,5 +25,6 @@ class UserStoryPermission(TaigaResourcePermission):
update_perms = HasProjectPerm('modify_us') update_perms = HasProjectPerm('modify_us')
destroy_perms = HasProjectPerm('delete_us') destroy_perms = HasProjectPerm('delete_us')
list_perms = AllowAny() list_perms = AllowAny()
csv_perms = AllowAny()
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us')) bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
bulk_update_order_perms = HasProjectPerm('modify_us') bulk_update_order_perms = HasProjectPerm('modify_us')

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import csv
import io
from django.utils import timezone from django.utils import timezone
from taiga.base.utils import db, text from taiga.base.utils import db, text
@ -104,3 +107,64 @@ def open_userstory(us):
us.is_closed = False us.is_closed = False
us.finish_date = None us.finish_date = None
us.save(update_fields=["is_closed", "finish_date"]) us.save(update_fields=["is_closed", "finish_date"])
def userstories_to_csv(project,queryset):
csv_data = io.StringIO()
fieldnames = ["ref", "subject", "description", "milestone", "owner",
"owner_full_name", "assigned_to", "assigned_to_full_name",
"status", "is_closed"]
for role in project.roles.filter(computable=True).order_by('name'):
fieldnames.append("{}-points".format(role.slug))
fieldnames.append("total-points")
fieldnames += ["backlog_order", "sprint_order", "kanban_order",
"created_date", "modified_date", "finish_date",
"client_requirement", "team_requirement", "attachments",
"generated_from_issue", "external_reference", "tasks"]
for custom_attr in project.userstorycustomattributes.all():
fieldnames.append(custom_attr.name)
writer = csv.DictWriter(csv_data, fieldnames=fieldnames)
writer.writeheader()
for us in queryset:
row = {
"ref": us.ref,
"subject": us.subject,
"description": us.description,
"milestone": us.milestone.name if us.milestone else None,
"owner": us.owner.username,
"owner_full_name": us.owner.get_full_name(),
"assigned_to": us.assigned_to.username if us.assigned_to else None,
"assigned_to_full_name": us.assigned_to.get_full_name() if us.assigned_to else None,
"status": us.status.name,
"is_closed": us.is_closed,
"backlog_order": us.backlog_order,
"sprint_order": us.sprint_order,
"kanban_order": us.kanban_order,
"created_date": us.created_date,
"modified_date": us.modified_date,
"finish_date": us.finish_date,
"client_requirement": us.client_requirement,
"team_requirement": us.team_requirement,
"attachments": us.attachments.count(),
"generated_from_issue": us.generated_from_issue.ref if us.generated_from_issue else None,
"external_reference": us.external_reference,
"tasks": ",".join([str(task.ref) for task in us.tasks.all()]),
}
for role in us.project.roles.filter(computable=True).order_by('name'):
if us.role_points.filter(role_id=role.id).count() == 1:
row["{}-points".format(role.slug)] = us.role_points.get(role_id=role.id).points.value
else:
row["{}-points".format(role.slug)] = 0
row['total-points'] = us.get_total_points()
for custom_attr in project.userstorycustomattributes.all():
value = us.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
row[custom_attr.name] = value
writer.writerow(row)
return csv_data

View File

@ -1,3 +1,5 @@
import uuid
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from taiga.projects.issues.serializers import IssueSerializer from taiga.projects.issues.serializers import IssueSerializer
@ -36,15 +38,18 @@ 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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
issues_csv_uuid=uuid.uuid4().hex)
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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
issues_csv_uuid=uuid.uuid4().hex)
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,
issues_csv_uuid=uuid.uuid4().hex)
m.public_membership = f.MembershipFactory(project=m.public_project, m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms, user=m.project_member_with_perms,
@ -437,3 +442,27 @@ def test_issue_voters_retrieve(client, data):
results = helper_test_http_method(client, 'get', private_url2, None, users) results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]
def test_issues_csv(client, data):
url = reverse('issues-csv')
csv_public_uuid = data.public_project.issues_csv_uuid
csv_private1_uuid = data.private_project1.issues_csv_uuid
csv_private2_uuid = data.private_project1.issues_csv_uuid
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_public_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private1_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private2_uuid), None, users)
assert results == [200, 200, 200, 200, 200]

View File

@ -369,3 +369,66 @@ def test_invitations_retrieve(client, data):
] ]
results = helper_test_http_method(client, 'get', url, None, users) results = helper_test_http_method(client, 'get', url, None, users)
assert results == [200, 200, 200, 200] assert results == [200, 200, 200, 200]
def test_regenerate_userstories_csv_uuid(client, data):
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})
private2_url = reverse('projects-regenerate-userstories-csv-uuid', kwargs={"pk": data.private_project2.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]
def test_regenerate_tasks_csv_uuid(client, data):
public_url = reverse('projects-regenerate-tasks-csv-uuid', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-regenerate-tasks-csv-uuid', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-regenerate-tasks-csv-uuid', kwargs={"pk": data.private_project2.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]
def test_regenerate_issues_csv_uuid(client, data):
public_url = reverse('projects-regenerate-issues-csv-uuid', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-regenerate-issues-csv-uuid', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-regenerate-issues-csv-uuid', kwargs={"pk": data.private_project2.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]

View File

@ -1,3 +1,5 @@
import uuid
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from taiga.base.utils import json from taiga.base.utils import json
@ -35,15 +37,18 @@ 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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
tasks_csv_uuid=uuid.uuid4().hex)
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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
tasks_csv_uuid=uuid.uuid4().hex)
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,
tasks_csv_uuid=uuid.uuid4().hex)
m.public_membership = f.MembershipFactory(project=m.public_project, m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms, user=m.project_member_with_perms,
@ -307,3 +312,27 @@ def test_task_action_bulk_create(client, data):
}) })
results = helper_test_http_method(client, 'post', url, bulk_data, users) results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]
def test_tasks_csv(client, data):
url = reverse('tasks-csv')
csv_public_uuid = data.public_project.tasks_csv_uuid
csv_private1_uuid = data.private_project1.tasks_csv_uuid
csv_private2_uuid = data.private_project1.tasks_csv_uuid
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_public_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private1_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private2_uuid), None, users)
assert results == [200, 200, 200, 200, 200]

View File

@ -1,3 +1,5 @@
import uuid
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from taiga.base.utils import json from taiga.base.utils import json
@ -35,15 +37,18 @@ 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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
userstories_csv_uuid=uuid.uuid4().hex)
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], USER_PERMISSIONS)), public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner) owner=m.project_owner,
userstories_csv_uuid=uuid.uuid4().hex)
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,
userstories_csv_uuid=uuid.uuid4().hex)
m.public_membership = f.MembershipFactory(project=m.public_project, m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms, user=m.project_member_with_perms,
@ -308,3 +313,27 @@ def test_user_story_action_bulk_update_order(client, data):
}) })
results = helper_test_http_method(client, 'post', url, post_data, users) results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204] assert results == [401, 403, 403, 204, 204]
def test_user_stories_csv(client, data):
url = reverse('userstories-csv')
csv_public_uuid = data.public_project.userstories_csv_uuid
csv_private1_uuid = data.private_project1.userstories_csv_uuid
csv_private2_uuid = data.private_project1.userstories_csv_uuid
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_public_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private1_uuid), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?uuid={}".format(url, csv_private2_uuid), None, users)
assert results == [200, 200, 200, 200, 200]

View File

@ -1,3 +1,6 @@
import uuid
import csv
from unittest import mock from unittest import mock
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -152,7 +155,6 @@ def test_api_filter_by_text_6(client):
issue = f.create_issue(subject="test", owner=user) issue = f.create_issue(subject="test", owner=user)
issue.ref = 123 issue.ref = 123
issue.save() issue.save()
print(issue.ref, issue.subject)
url = reverse("issues-list") + "?q=%s" % (issue.ref) url = reverse("issues-list") + "?q=%s" % (issue.ref)
client.login(issue.owner) client.login(issue.owner)
@ -161,3 +163,38 @@ def test_api_filter_by_text_6(client):
assert response.status_code == 200 assert response.status_code == 200
assert number_of_issues == 1 assert number_of_issues == 1
def test_get_invalid_csv(client):
url = reverse("issues-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("issues-csv")
project = f.ProjectFactory.create(issues_csv_uuid=uuid.uuid4().hex)
response = client.get("{}?uuid={}".format(url, project.issues_csv_uuid))
assert response.status_code == 200
def test_custom_fields_csv_generation():
project = f.ProjectFactory.create(issues_csv_uuid=uuid.uuid4().hex)
attr = f.IssueCustomAttributeFactory.create(project=project, name="attr1", description="desc")
issue = f.IssueFactory.create(project=project)
attr_values = issue.custom_attributes_values
attr_values.attributes_values = {str(attr.id):"val1"}
attr_values.save()
queryset = project.issues.all()
data = services.issues_to_csv(project, queryset)
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[15] == attr.name
row = next(reader)
assert row[15] == "val1"

View File

@ -1,3 +1,6 @@
import uuid
import csv
from unittest import mock from unittest import mock
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -110,3 +113,38 @@ def test_api_update_order_in_bulk(client):
assert response1.status_code == 204, response1.data assert response1.status_code == 204, response1.data
assert response2.status_code == 204, response2.data assert response2.status_code == 204, response2.data
def test_get_invalid_csv(client):
url = reverse("tasks-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("tasks-csv")
project = f.ProjectFactory.create(tasks_csv_uuid=uuid.uuid4().hex)
response = client.get("{}?uuid={}".format(url, project.tasks_csv_uuid))
assert response.status_code == 200
def test_custom_fields_csv_generation():
project = f.ProjectFactory.create(tasks_csv_uuid=uuid.uuid4().hex)
attr = f.TaskCustomAttributeFactory.create(project=project, name="attr1", description="desc")
task = f.TaskFactory.create(project=project)
attr_values = task.custom_attributes_values
attr_values.attributes_values = {str(attr.id):"val1"}
attr_values.save()
queryset = project.tasks.all()
data = services.tasks_to_csv(project, queryset)
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[16] == attr.name
row = next(reader)
assert row[16] == "val1"

View File

@ -1,4 +1,7 @@
import copy import copy
import uuid
import csv
from unittest import mock from unittest import mock
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -241,3 +244,38 @@ def test_get_total_points(client):
f.RolePointsFactory.create(user_story=us_mixed, role=role2, points=points2) f.RolePointsFactory.create(user_story=us_mixed, role=role2, points=points2)
assert us_mixed.get_total_points() == 1.0 assert us_mixed.get_total_points() == 1.0
def test_get_invalid_csv(client):
url = reverse("userstories-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("userstories-csv")
project = f.ProjectFactory.create(userstories_csv_uuid=uuid.uuid4().hex)
response = client.get("{}?uuid={}".format(url, project.userstories_csv_uuid))
assert response.status_code == 200
def test_custom_fields_csv_generation():
project = f.ProjectFactory.create(userstories_csv_uuid=uuid.uuid4().hex)
attr = f.UserStoryCustomAttributeFactory.create(project=project, name="attr1", description="desc")
us = f.UserStoryFactory.create(project=project)
attr_values = us.custom_attributes_values
attr_values.attributes_values = {str(attr.id):"val1"}
attr_values.save()
queryset = project.user_stories.all()
data = services.userstories_to_csv(project, queryset)
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[23] == attr.name
row = next(reader)
assert row[23] == "val1"