serializers.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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 taiga.base.api import serializers
  19. from taiga.base.fields import JsonField
  20. from taiga.base.fields import PgArrayField
  21. from taiga.base.fields import TagsField
  22. from taiga.base.fields import TagsColorsField
  23. from taiga.projects.notifications.validators import WatchersValidator
  24. from taiga.users.services import get_photo_or_gravatar_url
  25. from taiga.users.serializers import UserSerializer
  26. from taiga.users.serializers import UserBasicInfoSerializer
  27. from taiga.users.serializers import ProjectRoleSerializer
  28. from taiga.users.validators import RoleExistsValidator
  29. from taiga.permissions.service import get_user_project_permissions
  30. from taiga.permissions.service import is_project_owner
  31. from . import models
  32. from . import services
  33. from .validators import ProjectExistsValidator
  34. from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
  35. from .custom_attributes.serializers import TaskCustomAttributeSerializer
  36. from .custom_attributes.serializers import IssueCustomAttributeSerializer
  37. from .notifications.mixins import WatchedResourceModelSerializer
  38. from .votes.mixins.serializers import StarredResourceSerializerMixin
  39. ######################################################
  40. ## Custom values for selectors
  41. ######################################################
  42. class PointsSerializer(serializers.ModelSerializer):
  43. class Meta:
  44. model = models.Points
  45. i18n_fields = ("name",)
  46. def validate_name(self, attrs, source):
  47. """
  48. Check the points name is not duplicated in the project on creation
  49. """
  50. qs = None
  51. # If the user story status exists:
  52. if self.object and attrs.get("name", None):
  53. qs = models.Points.objects.filter(project=self.object.project, name=attrs[source])
  54. if not self.object and attrs.get("project", None) and attrs.get("name", None):
  55. qs = models.Points.objects.filter(project=attrs["project"], name=attrs[source])
  56. if qs and qs.exists():
  57. raise serializers.ValidationError(_("Name duplicated for the project"))
  58. return attrs
  59. class UserStoryStatusSerializer(serializers.ModelSerializer):
  60. class Meta:
  61. model = models.UserStoryStatus
  62. i18n_fields = ("name",)
  63. def validate_name(self, attrs, source):
  64. """
  65. Check the status name is not duplicated in the project on creation
  66. """
  67. qs = None
  68. # If the user story status exists:
  69. if self.object and attrs.get("name", None):
  70. qs = models.UserStoryStatus.objects.filter(project=self.object.project,
  71. name=attrs[source])
  72. if not self.object and attrs.get("project", None) and attrs.get("name", None):
  73. qs = models.UserStoryStatus.objects.filter(project=attrs["project"],
  74. name=attrs[source])
  75. if qs and qs.exists():
  76. raise serializers.ValidationError(_("Name duplicated for the project"))
  77. return attrs
  78. class BasicUserStoryStatusSerializer(serializers.ModelSerializer):
  79. class Meta:
  80. model = models.UserStoryStatus
  81. i18n_fields = ("name",)
  82. fields = ("name", "color")
  83. class TaskStatusSerializer(serializers.ModelSerializer):
  84. class Meta:
  85. model = models.TaskStatus
  86. i18n_fields = ("name",)
  87. def validate_name(self, attrs, source):
  88. """
  89. Check the task name is not duplicated in the project on creation
  90. """
  91. qs = None
  92. # If the user story status exists:
  93. if self.object and attrs.get("name", None):
  94. qs = models.TaskStatus.objects.filter(project=self.object.project, name=attrs[source])
  95. if not self.object and attrs.get("project", None) and attrs.get("name", None):
  96. qs = models.TaskStatus.objects.filter(project=attrs["project"], name=attrs[source])
  97. if qs and qs.exists():
  98. raise serializers.ValidationError(_("Name duplicated for the project"))
  99. return attrs
  100. class BasicTaskStatusSerializerSerializer(serializers.ModelSerializer):
  101. class Meta:
  102. model = models.TaskStatus
  103. i18n_fields = ("name",)
  104. fields = ("name", "color")
  105. class SeveritySerializer(serializers.ModelSerializer):
  106. class Meta:
  107. model = models.Severity
  108. i18n_fields = ("name",)
  109. class PrioritySerializer(serializers.ModelSerializer):
  110. class Meta:
  111. model = models.Priority
  112. i18n_fields = ("name",)
  113. class IssueStatusSerializer(serializers.ModelSerializer):
  114. class Meta:
  115. model = models.IssueStatus
  116. i18n_fields = ("name",)
  117. def validate_name(self, attrs, source):
  118. """
  119. Check the issue name is not duplicated in the project on creation
  120. """
  121. qs = None
  122. # If the user story status exists:
  123. if self.object and attrs.get("name", None):
  124. qs = models.IssueStatus.objects.filter(project=self.object.project, name=attrs[source])
  125. if not self.object and attrs.get("project", None) and attrs.get("name", None):
  126. qs = models.IssueStatus.objects.filter(project=attrs["project"], name=attrs[source])
  127. if qs and qs.exists():
  128. raise serializers.ValidationError(_("Name duplicated for the project"))
  129. return attrs
  130. class BasicIssueStatusSerializer(serializers.ModelSerializer):
  131. class Meta:
  132. model = models.IssueStatus
  133. i18n_fields = ("name",)
  134. fields = ("name", "color")
  135. class IssueTypeSerializer(serializers.ModelSerializer):
  136. class Meta:
  137. model = models.IssueType
  138. i18n_fields = ("name",)
  139. ######################################################
  140. ## Members
  141. ######################################################
  142. class MembershipSerializer(serializers.ModelSerializer):
  143. role_name = serializers.CharField(source='role.name', required=False, read_only=True, i18n=True)
  144. full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True)
  145. user_email = serializers.EmailField(source='user.email', required=False, read_only=True)
  146. is_user_active = serializers.BooleanField(source='user.is_active', required=False,
  147. read_only=True)
  148. email = serializers.EmailField(required=True)
  149. color = serializers.CharField(source='user.color', required=False, read_only=True)
  150. photo = serializers.SerializerMethodField("get_photo")
  151. project_name = serializers.SerializerMethodField("get_project_name")
  152. project_slug = serializers.SerializerMethodField("get_project_slug")
  153. invited_by = UserBasicInfoSerializer(read_only=True)
  154. class Meta:
  155. model = models.Membership
  156. # IMPORTANT: Maintain the MembershipAdminSerializer Meta up to date
  157. # with this info (excluding here user_email and email)
  158. read_only_fields = ("user",)
  159. exclude = ("token", "user_email", "email")
  160. def get_photo(self, project):
  161. return get_photo_or_gravatar_url(project.user)
  162. def get_project_name(self, obj):
  163. return obj.project.name if obj and obj.project else ""
  164. def get_project_slug(self, obj):
  165. return obj.project.slug if obj and obj.project else ""
  166. def validate_email(self, attrs, source):
  167. project = attrs.get("project", None)
  168. if project is None:
  169. project = self.object.project
  170. email = attrs[source]
  171. qs = models.Membership.objects.all()
  172. # If self.object is not None, the serializer is in update
  173. # mode, and for it, it should exclude self.
  174. if self.object:
  175. qs = qs.exclude(pk=self.object.pk)
  176. qs = qs.filter(Q(project_id=project.id, user__email=email) |
  177. Q(project_id=project.id, email=email))
  178. if qs.count() > 0:
  179. raise serializers.ValidationError(_("Email address is already taken"))
  180. return attrs
  181. def validate_role(self, attrs, source):
  182. project = attrs.get("project", None)
  183. if project is None:
  184. project = self.object.project
  185. role = attrs[source]
  186. if project.roles.filter(id=role.id).count() == 0:
  187. raise serializers.ValidationError(_("Invalid role for the project"))
  188. return attrs
  189. def validate_is_owner(self, attrs, source):
  190. is_owner = attrs[source]
  191. project = attrs.get("project", None)
  192. if project is None:
  193. project = self.object.project
  194. if (self.object and
  195. not services.project_has_valid_owners(project, exclude_user=self.object.user)):
  196. raise serializers.ValidationError(_("At least one of the user must be an active admin"))
  197. return attrs
  198. class MembershipAdminSerializer(MembershipSerializer):
  199. class Meta:
  200. model = models.Membership
  201. # IMPORTANT: Maintain the MembershipSerializer Meta up to date
  202. # with this info (excluding there user_email and email)
  203. read_only_fields = ("user",)
  204. exclude = ("token",)
  205. class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer):
  206. email = serializers.EmailField()
  207. role_id = serializers.IntegerField()
  208. class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer):
  209. project_id = serializers.IntegerField()
  210. bulk_memberships = MemberBulkSerializer(many=True)
  211. invitation_extra_text = serializers.CharField(required=False, max_length=255)
  212. class ProjectMemberSerializer(serializers.ModelSerializer):
  213. id = serializers.IntegerField(source="user.id", read_only=True)
  214. username = serializers.CharField(source='user.username', read_only=True)
  215. full_name = serializers.CharField(source='user.full_name', read_only=True)
  216. full_name_display = serializers.CharField(source='user.get_full_name', read_only=True)
  217. color = serializers.CharField(source='user.color', read_only=True)
  218. photo = serializers.SerializerMethodField("get_photo")
  219. is_active = serializers.BooleanField(source='user.is_active', read_only=True)
  220. role_name = serializers.CharField(source='role.name', read_only=True, i18n=True)
  221. class Meta:
  222. model = models.Membership
  223. exclude = ("project", "email", "created_at", "token", "invited_by", "invitation_extra_text", "user_order")
  224. def get_photo(self, membership):
  225. return get_photo_or_gravatar_url(membership.user)
  226. ######################################################
  227. ## Projects
  228. ######################################################
  229. class ProjectSerializer(WatchersValidator, StarredResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer):
  230. tags = TagsField(default=[], required=False)
  231. anon_permissions = PgArrayField(required=False)
  232. public_permissions = PgArrayField(required=False)
  233. my_permissions = serializers.SerializerMethodField("get_my_permissions")
  234. i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
  235. tags_colors = TagsColorsField(required=False)
  236. total_closed_milestones = serializers.SerializerMethodField("get_total_closed_milestones")
  237. class Meta:
  238. model = models.Project
  239. read_only_fields = ("created_date", "modified_date", "owner", "slug")
  240. exclude = ("last_us_ref", "last_task_ref", "last_issue_ref",
  241. "issues_csv_uuid", "tasks_csv_uuid", "userstories_csv_uuid")
  242. def get_my_permissions(self, obj):
  243. if "request" in self.context:
  244. return get_user_project_permissions(self.context["request"].user, obj)
  245. return []
  246. def get_i_am_owner(self, obj):
  247. if "request" in self.context:
  248. return is_project_owner(self.context["request"].user, obj)
  249. return False
  250. def get_total_closed_milestones(self, obj):
  251. return obj.milestones.filter(closed=True).count()
  252. def validate_total_milestones(self, attrs, source):
  253. """
  254. Check that total_milestones is not null, it's an optional parameter but
  255. not nullable in the model.
  256. """
  257. value = attrs[source]
  258. if value is None:
  259. raise serializers.ValidationError(_("Total milestones must be major or equal to zero"))
  260. return attrs
  261. class ProjectDetailSerializer(ProjectSerializer):
  262. us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories
  263. points = PointsSerializer(many=True, required=False)
  264. task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks
  265. issue_statuses = IssueStatusSerializer(many=True, required=False)
  266. issue_types = IssueTypeSerializer(many=True, required=False)
  267. priorities = PrioritySerializer(many=True, required=False) # Issues
  268. severities = SeveritySerializer(many=True, required=False)
  269. userstory_custom_attributes = UserStoryCustomAttributeSerializer(source="userstorycustomattributes",
  270. many=True, required=False)
  271. task_custom_attributes = TaskCustomAttributeSerializer(source="taskcustomattributes",
  272. many=True, required=False)
  273. issue_custom_attributes = IssueCustomAttributeSerializer(source="issuecustomattributes",
  274. many=True, required=False)
  275. roles = ProjectRoleSerializer(source="roles", many=True, read_only=True)
  276. members = serializers.SerializerMethodField(method_name="get_members")
  277. def get_members(self, obj):
  278. qs = obj.memberships.filter(user__isnull=False)
  279. qs = qs.extra(select={"complete_user_name":"concat(full_name, username)"})
  280. qs = qs.order_by("complete_user_name")
  281. qs = qs.select_related("role", "user")
  282. serializer = ProjectMemberSerializer(qs, many=True)
  283. return serializer.data
  284. class ProjectDetailAdminSerializer(ProjectDetailSerializer):
  285. class Meta:
  286. model = models.Project
  287. read_only_fields = ("created_date", "modified_date", "owner", "slug")
  288. exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
  289. ######################################################
  290. ## Starred
  291. ######################################################
  292. class StarredSerializer(serializers.ModelSerializer):
  293. class Meta:
  294. model = models.Project
  295. fields = ['id', 'name', 'slug']
  296. ######################################################
  297. ## Project Templates
  298. ######################################################
  299. class ProjectTemplateSerializer(serializers.ModelSerializer):
  300. default_options = JsonField(required=False, label=_("Default options"))
  301. us_statuses = JsonField(required=False, label=_("User story's statuses"))
  302. points = JsonField(required=False, label=_("Points"))
  303. task_statuses = JsonField(required=False, label=_("Task's statuses"))
  304. issue_statuses = JsonField(required=False, label=_("Issue's statuses"))
  305. issue_types = JsonField(required=False, label=_("Issue's types"))
  306. priorities = JsonField(required=False, label=_("Priorities"))
  307. severities = JsonField(required=False, label=_("Severities"))
  308. roles = JsonField(required=False, label=_("Roles"))
  309. class Meta:
  310. model = models.ProjectTemplate
  311. read_only_fields = ("created_date", "modified_date")
  312. i18n_fields = ("name", "description")
  313. ######################################################
  314. ## Project order bulk serializers
  315. ######################################################
  316. class UpdateProjectOrderBulkSerializer(ProjectExistsValidator, serializers.Serializer):
  317. project_id = serializers.IntegerField()
  318. order = serializers.IntegerField()