Merge pull request #263 from taigaio/us/1913/csv-reports
US#1913: CSV Reportsremotes/origin/enhancement/email-actions
commit
6e780bb720
|
@ -5,6 +5,7 @@
|
|||
|
||||
### Features
|
||||
- Added custom fields per project for user stories, tasks and issues.
|
||||
- Support of export to CSV user stories, tasks and issues.
|
||||
- Allow public projects.
|
||||
|
||||
### Misc
|
||||
|
|
|
@ -53,6 +53,7 @@ from .votes.utils import attach_votescount_to_queryset
|
|||
|
||||
class ProjectViewSet(ModelCrudViewSet):
|
||||
serializer_class = serializers.ProjectDetailSerializer
|
||||
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
||||
list_serializer_class = serializers.ProjectSerializer
|
||||
permission_classes = (permissions.ProjectPermission, )
|
||||
filter_backends = (filters.CanViewProjectObjFilterBackend,)
|
||||
|
@ -61,6 +62,23 @@ class ProjectViewSet(ModelCrudViewSet):
|
|||
qs = models.Project.objects.all()
|
||||
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"])
|
||||
def by_slug(self, request):
|
||||
slug = request.QUERY_PARAMS.get("slug", None)
|
||||
|
@ -87,6 +105,33 @@ class ProjectViewSet(ModelCrudViewSet):
|
|||
self.check_permissions(request, "stats", 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"])
|
||||
def member_stats(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
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 exceptions as exc
|
||||
|
@ -24,7 +24,6 @@ from taiga.base import response
|
|||
from taiga.base.decorators import detail_route, list_route
|
||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||
from taiga.base.api.utils import get_object_or_404
|
||||
from taiga.base import tags
|
||||
|
||||
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)
|
||||
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"])
|
||||
def bulk_create(self, request, **kwargs):
|
||||
serializer = serializers.IssuesBulkSerializer(data=request.DATA)
|
||||
|
|
|
@ -28,6 +28,7 @@ class IssuePermission(TaigaResourcePermission):
|
|||
update_perms = HasProjectPerm('modify_issue')
|
||||
destroy_perms = HasProjectPerm('delete_issue')
|
||||
list_perms = AllowAny()
|
||||
csv_perms = AllowAny()
|
||||
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||
bulk_create_perms = HasProjectPerm('add_issue')
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
# 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 io
|
||||
import csv
|
||||
|
||||
from taiga.base.utils import db, text
|
||||
|
||||
from . import models
|
||||
|
@ -58,3 +61,42 @@ def update_issues_order_in_bulk(bulk_data):
|
|||
issue_ids.append(issue_id)
|
||||
new_order_values.append({"order": new_order_value})
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
]
|
|
@ -15,6 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import itertools
|
||||
import uuid
|
||||
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
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,
|
||||
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=[])
|
||||
_importing = None
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ class ProjectPermission(TaigaResourcePermission):
|
|||
list_perms = AllowAny()
|
||||
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()
|
||||
unstar_perms = IsAuthenticated()
|
||||
issues_stats_perms = HasProjectPerm('view_project')
|
||||
|
|
|
@ -269,7 +269,8 @@ class ProjectSerializer(ModelSerializer):
|
|||
class Meta:
|
||||
model = models.Project
|
||||
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):
|
||||
# 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")
|
||||
return attrs
|
||||
|
||||
|
||||
class ProjectDetailSerializer(ProjectSerializer):
|
||||
roles = serializers.SerializerMethodField("get_roles")
|
||||
memberships = serializers.SerializerMethodField("get_memberships")
|
||||
|
@ -331,6 +333,13 @@ class ProjectDetailSerializer(ProjectSerializer):
|
|||
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
|
||||
######################################################
|
||||
|
|
|
@ -22,6 +22,7 @@ from taiga.base import exceptions as exc
|
|||
from taiga.base.decorators import list_route
|
||||
from taiga.base.api import ModelCrudViewSet
|
||||
from taiga.projects.models import Project
|
||||
from django.http import HttpResponse
|
||||
|
||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||
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)
|
||||
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"])
|
||||
def bulk_create(self, request, **kwargs):
|
||||
serializer = serializers.TasksBulkSerializer(data=request.DATA)
|
||||
|
@ -98,8 +112,8 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
|||
|
||||
self.check_permissions(request, "bulk_update_order", project)
|
||||
services.update_tasks_order_in_bulk(data["bulk_tasks"],
|
||||
project=project,
|
||||
field=order_field)
|
||||
project=project,
|
||||
field=order_field)
|
||||
services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user)
|
||||
|
||||
return response.NoContent()
|
||||
|
|
|
@ -26,5 +26,6 @@ class TaskPermission(TaigaResourcePermission):
|
|||
update_perms = HasProjectPerm('modify_task')
|
||||
destroy_perms = HasProjectPerm('delete_task')
|
||||
list_perms = AllowAny()
|
||||
csv_perms = AllowAny()
|
||||
bulk_create_perms = HasProjectPerm('add_task')
|
||||
bulk_update_order_perms = HasProjectPerm('modify_task')
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
# 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 io
|
||||
import csv
|
||||
|
||||
from taiga.base.utils import db, text
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
from taiga.events import events
|
||||
|
@ -75,3 +78,42 @@ def snapshot_tasks_in_bulk(bulk_data, user):
|
|||
take_snapshot(task, user=user)
|
||||
except models.UserStory.DoesNotExist:
|
||||
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
|
||||
|
|
|
@ -22,6 +22,7 @@ from django.apps import apps
|
|||
from django.db import transaction
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponse
|
||||
|
||||
from taiga.base import filters
|
||||
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)
|
||||
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"])
|
||||
def bulk_create(self, request, **kwargs):
|
||||
serializer = serializers.UserStoriesBulkSerializer(data=request.DATA)
|
||||
|
|
|
@ -25,5 +25,6 @@ class UserStoryPermission(TaigaResourcePermission):
|
|||
update_perms = HasProjectPerm('modify_us')
|
||||
destroy_perms = HasProjectPerm('delete_us')
|
||||
list_perms = AllowAny()
|
||||
csv_perms = AllowAny()
|
||||
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
|
||||
bulk_update_order_perms = HasProjectPerm('modify_us')
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
# 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 csv
|
||||
import io
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from taiga.base.utils import db, text
|
||||
|
@ -104,3 +107,64 @@ def open_userstory(us):
|
|||
us.is_closed = False
|
||||
us.finish_date = None
|
||||
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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from taiga.projects.issues.serializers import IssueSerializer
|
||||
|
@ -36,15 +38,18 @@ def data():
|
|||
m.public_project = f.ProjectFactory(is_private=False,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_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,
|
||||
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)
|
||||
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]
|
||||
|
|
|
@ -369,3 +369,66 @@ def test_invitations_retrieve(client, data):
|
|||
]
|
||||
results = helper_test_http_method(client, 'get', url, None, users)
|
||||
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]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from taiga.base.utils import json
|
||||
|
@ -35,15 +37,18 @@ def data():
|
|||
m.public_project = f.ProjectFactory(is_private=False,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_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,
|
||||
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)
|
||||
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]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from taiga.base.utils import json
|
||||
|
@ -35,15 +37,18 @@ def data():
|
|||
m.public_project = f.ProjectFactory(is_private=False,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_permissions=list(map(lambda x: x[0], ANON_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,
|
||||
anon_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,
|
||||
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)
|
||||
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]
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import uuid
|
||||
import csv
|
||||
|
||||
from unittest import mock
|
||||
|
||||
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.ref = 123
|
||||
issue.save()
|
||||
print(issue.ref, issue.subject)
|
||||
url = reverse("issues-list") + "?q=%s" % (issue.ref)
|
||||
|
||||
client.login(issue.owner)
|
||||
|
@ -161,3 +163,38 @@ def test_api_filter_by_text_6(client):
|
|||
|
||||
assert response.status_code == 200
|
||||
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"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import uuid
|
||||
import csv
|
||||
|
||||
from unittest import mock
|
||||
|
||||
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 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"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import copy
|
||||
import uuid
|
||||
import csv
|
||||
|
||||
from unittest import mock
|
||||
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)
|
||||
|
||||
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"
|
||||
|
|
Loading…
Reference in New Issue