api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. from django.utils.translation import ugettext as _
  17. from django.db.models import Q
  18. from django.http import HttpResponse
  19. from taiga.base import filters
  20. from taiga.base import exceptions as exc
  21. from taiga.base import response
  22. from taiga.base.decorators import detail_route, list_route
  23. from taiga.base.api import ModelCrudViewSet, ModelListViewSet
  24. from taiga.base.api.utils import get_object_or_404
  25. from taiga.users.models import User
  26. from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
  27. from taiga.projects.occ import OCCResourceMixin
  28. from taiga.projects.history.mixins import HistoryResourceMixin
  29. from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType
  30. from taiga.projects.milestones.models import Milestone
  31. from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
  32. from . import models
  33. from . import services
  34. from . import permissions
  35. from . import serializers
  36. class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
  37. ModelCrudViewSet):
  38. queryset = models.Issue.objects.all()
  39. permission_classes = (permissions.IssuePermission, )
  40. filter_backends = (filters.CanViewIssuesFilterBackend,
  41. filters.OwnersFilter,
  42. filters.AssignedToFilter,
  43. filters.StatusesFilter,
  44. filters.IssueTypesFilter,
  45. filters.SeveritiesFilter,
  46. filters.PrioritiesFilter,
  47. filters.TagsFilter,
  48. filters.WatchersFilter,
  49. filters.QFilter,
  50. filters.OrderByFilterMixin)
  51. retrieve_exclude_filters = (filters.OwnersFilter,
  52. filters.AssignedToFilter,
  53. filters.StatusesFilter,
  54. filters.IssueTypesFilter,
  55. filters.SeveritiesFilter,
  56. filters.PrioritiesFilter,
  57. filters.TagsFilter,
  58. filters.WatchersFilter,)
  59. filter_fields = ("project",
  60. "status__is_closed")
  61. order_by_fields = ("type",
  62. "status",
  63. "severity",
  64. "priority",
  65. "created_date",
  66. "modified_date",
  67. "owner",
  68. "assigned_to",
  69. "subject")
  70. def get_serializer_class(self, *args, **kwargs):
  71. if self.action in ["retrieve", "by_ref"]:
  72. return serializers.IssueNeighborsSerializer
  73. if self.action == "list":
  74. return serializers.IssueListSerializer
  75. return serializers.IssueSerializer
  76. def update(self, request, *args, **kwargs):
  77. self.object = self.get_object_or_none()
  78. project_id = request.DATA.get('project', None)
  79. if project_id and self.object and self.object.project.id != project_id:
  80. try:
  81. new_project = Project.objects.get(pk=project_id)
  82. self.check_permissions(request, "destroy", self.object)
  83. self.check_permissions(request, "create", new_project)
  84. sprint_id = request.DATA.get('milestone', None)
  85. if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0:
  86. request.DATA['milestone'] = None
  87. status_id = request.DATA.get('status', None)
  88. if status_id is not None:
  89. try:
  90. old_status = self.object.project.issue_statuses.get(pk=status_id)
  91. new_status = new_project.issue_statuses.get(slug=old_status.slug)
  92. request.DATA['status'] = new_status.id
  93. except IssueStatus.DoesNotExist:
  94. request.DATA['status'] = new_project.default_issue_status.id
  95. priority_id = request.DATA.get('priority', None)
  96. if priority_id is not None:
  97. try:
  98. old_priority = self.object.project.priorities.get(pk=priority_id)
  99. new_priority = new_project.priorities.get(name=old_priority.name)
  100. request.DATA['priority'] = new_priority.id
  101. except Priority.DoesNotExist:
  102. request.DATA['priority'] = new_project.default_priority.id
  103. severity_id = request.DATA.get('severity', None)
  104. if severity_id is not None:
  105. try:
  106. old_severity = self.object.project.severities.get(pk=severity_id)
  107. new_severity = new_project.severities.get(name=old_severity.name)
  108. request.DATA['severity'] = new_severity.id
  109. except Severity.DoesNotExist:
  110. request.DATA['severity'] = new_project.default_severity.id
  111. type_id = request.DATA.get('type', None)
  112. if type_id is not None:
  113. try:
  114. old_type = self.object.project.issue_types.get(pk=type_id)
  115. new_type = new_project.issue_types.get(name=old_type.name)
  116. request.DATA['type'] = new_type.id
  117. except IssueType.DoesNotExist:
  118. request.DATA['type'] = new_project.default_issue_type.id
  119. except Project.DoesNotExist:
  120. return response.BadRequest(_("The project doesn't exist"))
  121. return super().update(request, *args, **kwargs)
  122. def get_queryset(self):
  123. qs = super().get_queryset()
  124. qs = qs.prefetch_related("attachments")
  125. qs = self.attach_votes_attrs_to_queryset(qs)
  126. return self.attach_watchers_attrs_to_queryset(qs)
  127. def pre_save(self, obj):
  128. if not obj.id:
  129. obj.owner = self.request.user
  130. super().pre_save(obj)
  131. def pre_conditions_on_save(self, obj):
  132. super().pre_conditions_on_save(obj)
  133. if obj.milestone and obj.milestone.project != obj.project:
  134. raise exc.PermissionDenied(_("You don't have permissions to set this sprint "
  135. "to this issue."))
  136. if obj.status and obj.status.project != obj.project:
  137. raise exc.PermissionDenied(_("You don't have permissions to set this status "
  138. "to this issue."))
  139. if obj.severity and obj.severity.project != obj.project:
  140. raise exc.PermissionDenied(_("You don't have permissions to set this severity "
  141. "to this issue."))
  142. if obj.priority and obj.priority.project != obj.project:
  143. raise exc.PermissionDenied(_("You don't have permissions to set this priority "
  144. "to this issue."))
  145. if obj.type and obj.type.project != obj.project:
  146. raise exc.PermissionDenied(_("You don't have permissions to set this type "
  147. "to this issue."))
  148. @list_route(methods=["GET"])
  149. def by_ref(self, request):
  150. ref = request.QUERY_PARAMS.get("ref", None)
  151. project_id = request.QUERY_PARAMS.get("project", None)
  152. issue = get_object_or_404(models.Issue, ref=ref, project_id=project_id)
  153. return self.retrieve(request, pk=issue.pk)
  154. @list_route(methods=["GET"])
  155. def filters_data(self, request, *args, **kwargs):
  156. project_id = request.QUERY_PARAMS.get("project", None)
  157. project = get_object_or_404(Project, id=project_id)
  158. filter_backends = self.get_filter_backends()
  159. types_filter_backends = (f for f in filter_backends if f != filters.IssueTypesFilter)
  160. statuses_filter_backends = (f for f in filter_backends if f != filters.StatusesFilter)
  161. assigned_to_filter_backends = (f for f in filter_backends if f != filters.AssignedToFilter)
  162. owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
  163. priorities_filter_backends = (f for f in filter_backends if f != filters.PrioritiesFilter)
  164. severities_filter_backends = (f for f in filter_backends if f != filters.SeveritiesFilter)
  165. tags_filter_backends = (f for f in filter_backends if f != filters.TagsFilter)
  166. queryset = self.get_queryset()
  167. querysets = {
  168. "types": self.filter_queryset(queryset, filter_backends=types_filter_backends),
  169. "statuses": self.filter_queryset(queryset, filter_backends=statuses_filter_backends),
  170. "assigned_to": self.filter_queryset(queryset, filter_backends=assigned_to_filter_backends),
  171. "owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
  172. "priorities": self.filter_queryset(queryset, filter_backends=priorities_filter_backends),
  173. "severities": self.filter_queryset(queryset, filter_backends=severities_filter_backends),
  174. "tags": self.filter_queryset(queryset)
  175. }
  176. return response.Ok(services.get_issues_filters_data(project, querysets))
  177. @list_route(methods=["GET"])
  178. def csv(self, request):
  179. uuid = request.QUERY_PARAMS.get("uuid", None)
  180. if uuid is None:
  181. return response.NotFound()
  182. project = get_object_or_404(Project, issues_csv_uuid=uuid)
  183. queryset = project.issues.all().order_by('ref')
  184. data = services.issues_to_csv(project, queryset)
  185. csv_response = HttpResponse(data.getvalue(), content_type='application/csv; charset=utf-8')
  186. csv_response['Content-Disposition'] = 'attachment; filename="issues.csv"'
  187. return csv_response
  188. @list_route(methods=["POST"])
  189. def bulk_create(self, request, **kwargs):
  190. serializer = serializers.IssuesBulkSerializer(data=request.DATA)
  191. if serializer.is_valid():
  192. data = serializer.data
  193. project = Project.objects.get(pk=data["project_id"])
  194. self.check_permissions(request, 'bulk_create', project)
  195. issues = services.create_issues_in_bulk(
  196. data["bulk_issues"], project=project, owner=request.user,
  197. status=project.default_issue_status, severity=project.default_severity,
  198. priority=project.default_priority, type=project.default_issue_type,
  199. callback=self.post_save, precall=self.pre_save)
  200. issues_serialized = self.get_serializer_class()(issues, many=True)
  201. return response.Ok(data=issues_serialized.data)
  202. return response.BadRequest(serializer.errors)
  203. class IssueVotersViewSet(VotersViewSetMixin, ModelListViewSet):
  204. permission_classes = (permissions.IssueVotersPermission,)
  205. resource_model = models.Issue
  206. class IssueWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
  207. permission_classes = (permissions.IssueWatchersPermission,)
  208. resource_model = models.Issue