# Copyright (C) 2014 Andrey Antukh # Copyright (C) 2014 Jesús Espino # Copyright (C) 2014 David Barragán # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from os import path from django.utils.translation import ugettext_lazy as _ from django.db.models import Q from rest_framework import serializers from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer, TagsColorsField from taiga.users.models import Role, User from taiga.users.services import get_photo_or_gravatar_url from taiga.users.serializers import UserSerializer from taiga.users.validators import RoleExistsValidator from taiga.permissions.service import get_user_project_permissions, is_project_owner from . import models from . validators import ProjectExistsValidator # User Stories common serializers class PointsSerializer(ModelSerializer): class Meta: model = models.Points def validate_name(self, attrs, source): """ Check the points name is not duplicated in the project on creation """ qs = None # If the user story status exists: if self.object and attrs.get("name", None): qs = models.Points.objects.filter(project=self.object.project, name=attrs[source]) if not self.object and attrs.get("project", None) and attrs.get("name", None): qs = models.Points.objects.filter(project=attrs["project"], name=attrs[source]) if qs and qs.exists(): raise serializers.ValidationError("Name duplicated for the project") return attrs class UserStoryStatusSerializer(ModelSerializer): class Meta: model = models.UserStoryStatus def validate_name(self, attrs, source): """ Check the status name is not duplicated in the project on creation """ qs = None # If the user story status exists: if self.object and attrs.get("name", None): qs = models.UserStoryStatus.objects.filter(project=self.object.project, name=attrs[source]) if not self.object and attrs.get("project", None) and attrs.get("name", None): qs = models.UserStoryStatus.objects.filter(project=attrs["project"], name=attrs[source]) if qs and qs.exists(): raise serializers.ValidationError("Name duplicated for the project") return attrs # Task common serializers class TaskStatusSerializer(ModelSerializer): class Meta: model = models.TaskStatus def validate_name(self, attrs, source): """ Check the task name is not duplicated in the project on creation """ qs = None # If the user story status exists: if self.object and attrs.get("name", None): qs = models.TaskStatus.objects.filter(project=self.object.project, name=attrs[source]) if not self.object and attrs.get("project", None) and attrs.get("name", None): qs = models.TaskStatus.objects.filter(project=attrs["project"], name=attrs[source]) if qs and qs.exists(): raise serializers.ValidationError("Name duplicated for the project") return attrs # Issues common serializers class SeveritySerializer(ModelSerializer): class Meta: model = models.Severity class PrioritySerializer(ModelSerializer): class Meta: model = models.Priority class IssueStatusSerializer(ModelSerializer): class Meta: model = models.IssueStatus def validate_name(self, attrs, source): """ Check the issue name is not duplicated in the project on creation """ qs = None # If the user story status exists: if self.object and attrs.get("name", None): qs = models.IssueStatus.objects.filter(project=self.object.project, name=attrs[source]) if not self.object and attrs.get("project", None) and attrs.get("name", None): qs = models.IssueStatus.objects.filter(project=attrs["project"], name=attrs[source]) if qs and qs.exists(): raise serializers.ValidationError("Name duplicated for the project") return attrs class IssueTypeSerializer(ModelSerializer): class Meta: model = models.IssueType # Projects class MembershipSerializer(ModelSerializer): role_name = serializers.CharField(source='role.name', required=False, read_only=True) full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True) user_email = serializers.EmailField(source='user.email', required=False, read_only=True) email = serializers.EmailField(required=True) color = serializers.CharField(source='user.color', required=False, read_only=True) photo = serializers.SerializerMethodField("get_photo") project_name = serializers.SerializerMethodField("get_project_name") project_slug = serializers.SerializerMethodField("get_project_slug") invited_by = UserSerializer(read_only=True) class Meta: model = models.Membership read_only_fields = ("user",) exclude = ("token",) def get_photo(self, project): return get_photo_or_gravatar_url(project.user) def get_project_name(self, obj): return obj.project.name if obj and obj.project else "" def get_project_slug(self, obj): return obj.project.slug if obj and obj.project else "" def validate_email(self, attrs, source): project = attrs["project"] email = attrs[source] qs = models.Membership.objects.all() # If self.object is not None, the serializer is in update # mode, and for it, it should exclude self. if self.object: qs = qs.exclude(pk=self.object.pk) qs = qs.filter(Q(project_id=project.id, user__email=email) | Q(project_id=project.id, email=email)) if qs.count() > 0: raise serializers.ValidationError(_("Email address is already taken")) return attrs def validate_role(self, attrs, source): project = attrs["project"] role = attrs[source] if project.roles.filter(id=role.id).count() == 0: raise serializers.ValidationError(_("Invalid role for the project")) return attrs class ProjectMembershipSerializer(ModelSerializer): role_name = serializers.CharField(source='role.name', required=False) full_name = serializers.CharField(source='user.get_full_name', required=False) color = serializers.CharField(source='user.color', required=False) photo = serializers.SerializerMethodField("get_photo") class Meta: model = models.Membership def get_photo(self, project): return get_photo_or_gravatar_url(project.user) class ProjectSerializer(ModelSerializer): tags = PgArrayField(required=False) anon_permissions = PgArrayField(required=False) public_permissions = PgArrayField(required=False) stars = serializers.SerializerMethodField("get_stars_number") my_permissions = serializers.SerializerMethodField("get_my_permissions") i_am_owner = serializers.SerializerMethodField("get_i_am_owner") tags_colors = TagsColorsField(required=False) class Meta: model = models.Project read_only_fields = ("created_date", "modified_date", "owner") exclude = ("last_us_ref", "last_task_ref", "last_issue_ref") def get_stars_number(self, obj): # The "stars_count" attribute is attached in the get_queryset of the viewset. return getattr(obj, "stars_count", 0) def get_my_permissions(self, obj): if "request" in self.context: return get_user_project_permissions(self.context["request"].user, obj) return [] def get_i_am_owner(self, obj): if "request" in self.context: return is_project_owner(self.context["request"].user, obj) return False def validate_total_milestones(self, attrs, source): """ Check that total_milestones is not null, it's an optional parameter but not nullable in the model. """ value = attrs[source] if value is None: 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") us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories points = PointsSerializer(many=True, required=False) task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks issue_statuses = IssueStatusSerializer(many=True, required=False) issue_types = IssueTypeSerializer(many=True, required=False) priorities = PrioritySerializer(many=True, required=False) # Issues severities = SeveritySerializer(many=True, required=False) def get_memberships(self, obj): qs = obj.memberships.filter(user__isnull=False) qs = qs.order_by('user__full_name', 'user__username') qs = qs.select_related("role", "user") serializer = ProjectMembershipSerializer(qs, many=True) return serializer.data def get_roles(self, obj): serializer = ProjectRoleSerializer(obj.roles.all(), many=True) return serializer.data class ProjectRoleSerializer(ModelSerializer): class Meta: model = Role fields = ('id', 'name', 'slug', 'order', 'computable') class RoleSerializer(ModelSerializer): members_count = serializers.SerializerMethodField("get_members_count") permissions = PgArrayField(required=False) class Meta: model = Role fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count') def get_members_count(self, obj): return obj.memberships.count() class ProjectTemplateSerializer(ModelSerializer): default_options = JsonField(required=False, label=_("Default options")) us_statuses = JsonField(required=False, label=_("User story's statuses")) points = JsonField(required=False, label=_("Points")) task_statuses = JsonField(required=False, label=_("Task's statuses")) issue_statuses = JsonField(required=False, label=_("Issue's statuses")) issue_types = JsonField(required=False, label=_("Issue's types")) priorities = JsonField(required=False, label=_("Priorities")) severities = JsonField(required=False, label=_("Severities")) roles = JsonField(required=False, label=_("Roles")) class Meta: model = models.ProjectTemplate class StarredSerializer(ModelSerializer): class Meta: model = models.Project fields = ['id', 'name', 'slug'] class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer): email = serializers.EmailField() role_id = serializers.IntegerField() class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer): project_id = serializers.IntegerField() bulk_memberships = MemberBulkSerializer(many=True) invitation_extra_text = serializers.CharField(required=False, max_length=255)