458 lines
15 KiB
Python
458 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
|
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
|
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
|
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from taiga.base.api import serializers
|
|
from taiga.base.fields import Field, MethodField, I18NField
|
|
|
|
from taiga.permissions import services as permissions_services
|
|
from taiga.users.services import get_photo_url, get_user_photo_url
|
|
from taiga.users.gravatar import get_gravatar_id, get_user_gravatar_id
|
|
from taiga.users.serializers import UserBasicInfoSerializer
|
|
|
|
from taiga.permissions.services import calculate_permissions
|
|
from taiga.permissions.services import is_project_admin, is_project_owner
|
|
|
|
from . import services
|
|
from .notifications.choices import NotifyLevel
|
|
|
|
|
|
######################################################
|
|
# Custom values for selectors
|
|
######################################################
|
|
|
|
class PointsSerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
order = Field()
|
|
value = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class UserStoryStatusSerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
slug = Field()
|
|
order = Field()
|
|
is_closed = Field()
|
|
is_archived = Field()
|
|
color = Field()
|
|
wip_limit = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class TaskStatusSerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
slug = Field()
|
|
order = Field()
|
|
is_closed = Field()
|
|
color = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class SeveritySerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
order = Field()
|
|
color = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class PrioritySerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
order = Field()
|
|
color = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class IssueStatusSerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
slug = Field()
|
|
order = Field()
|
|
is_closed = Field()
|
|
color = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
class IssueTypeSerializer(serializers.LightSerializer):
|
|
name = I18NField()
|
|
order = Field()
|
|
color = Field()
|
|
project = Field(attr="project_id")
|
|
|
|
|
|
######################################################
|
|
# Members
|
|
######################################################
|
|
|
|
class MembershipDictSerializer(serializers.LightDictSerializer):
|
|
role_name = Field()
|
|
full_name = Field()
|
|
full_name_display = MethodField()
|
|
is_active = Field()
|
|
id = Field()
|
|
color = Field()
|
|
username = Field()
|
|
photo = MethodField()
|
|
gravatar_id = MethodField()
|
|
|
|
def get_full_name_display(self, obj):
|
|
return obj["full_name"] or obj["username"] or obj["email"]
|
|
|
|
def get_photo(self, obj):
|
|
return get_photo_url(obj['photo'])
|
|
|
|
def get_gravatar_id(self, obj):
|
|
return get_gravatar_id(obj['email'])
|
|
|
|
|
|
class MembershipSerializer(serializers.LightSerializer):
|
|
id = Field()
|
|
user = Field(attr="user_id")
|
|
project = Field(attr="project_id")
|
|
role = Field(attr="role_id")
|
|
is_admin = Field()
|
|
created_at = Field()
|
|
invited_by = Field(attr="invited_by_id")
|
|
invitation_extra_text = Field()
|
|
user_order = Field()
|
|
role_name = MethodField()
|
|
full_name = MethodField()
|
|
is_user_active = MethodField()
|
|
color = MethodField()
|
|
photo = MethodField()
|
|
gravatar_id = MethodField()
|
|
project_name = MethodField()
|
|
project_slug = MethodField()
|
|
invited_by = UserBasicInfoSerializer()
|
|
is_owner = MethodField()
|
|
|
|
def get_role_name(self, obj):
|
|
return obj.role.name if obj.role else None
|
|
|
|
def get_full_name(self, obj):
|
|
return obj.user.get_full_name() if obj.user else None
|
|
|
|
def get_is_user_active(self, obj):
|
|
return obj.user.is_active if obj.user else False
|
|
|
|
def get_color(self, obj):
|
|
return obj.user.color if obj.user else None
|
|
|
|
def get_photo(self, obj):
|
|
return get_user_photo_url(obj.user)
|
|
|
|
def get_gravatar_id(self, obj):
|
|
return get_user_gravatar_id(obj.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 get_is_owner(self, obj):
|
|
return (obj and obj.user_id and obj.project_id and obj.project.owner_id and
|
|
obj.user_id == obj.project.owner_id)
|
|
|
|
|
|
class MembershipAdminSerializer(MembershipSerializer):
|
|
email = Field()
|
|
user_email = MethodField()
|
|
|
|
def get_user_email(self, obj):
|
|
return obj.user.email if obj.user else None
|
|
|
|
# IMPORTANT: Maintain the MembershipSerializer Meta up to date
|
|
# with this info (excluding there user_email and email)
|
|
|
|
|
|
######################################################
|
|
# Projects
|
|
######################################################
|
|
|
|
class ProjectSerializer(serializers.LightSerializer):
|
|
id = Field()
|
|
name = Field()
|
|
slug = Field()
|
|
description = Field()
|
|
created_date = Field()
|
|
modified_date = Field()
|
|
owner = MethodField()
|
|
members = MethodField()
|
|
total_milestones = Field()
|
|
total_story_points = Field()
|
|
is_backlog_activated = Field()
|
|
is_kanban_activated = Field()
|
|
is_wiki_activated = Field()
|
|
is_issues_activated = Field()
|
|
videoconferences = Field()
|
|
videoconferences_extra_data = Field()
|
|
creation_template = Field(attr="creation_template_id")
|
|
is_private = Field()
|
|
anon_permissions = Field()
|
|
public_permissions = Field()
|
|
is_featured = Field()
|
|
is_looking_for_people = Field()
|
|
looking_for_people_note = Field()
|
|
blocked_code = Field()
|
|
totals_updated_datetime = Field()
|
|
total_fans = Field()
|
|
total_fans_last_week = Field()
|
|
total_fans_last_month = Field()
|
|
total_fans_last_year = Field()
|
|
total_activity = Field()
|
|
total_activity_last_week = Field()
|
|
total_activity_last_month = Field()
|
|
total_activity_last_year = Field()
|
|
|
|
tags = Field()
|
|
tags_colors = MethodField()
|
|
|
|
default_points = Field(attr="default_points_id")
|
|
default_us_status = Field(attr="default_us_status_id")
|
|
default_task_status = Field(attr="default_task_status_id")
|
|
default_priority = Field(attr="default_priority_id")
|
|
default_severity = Field(attr="default_severity_id")
|
|
default_issue_status = Field(attr="default_issue_status_id")
|
|
default_issue_type = Field(attr="default_issue_type_id")
|
|
|
|
my_permissions = MethodField()
|
|
|
|
i_am_owner = MethodField()
|
|
i_am_admin = MethodField()
|
|
i_am_member = MethodField()
|
|
|
|
notify_level = MethodField()
|
|
total_closed_milestones = MethodField()
|
|
|
|
is_watcher = MethodField()
|
|
total_watchers = MethodField()
|
|
|
|
logo_small_url = MethodField()
|
|
logo_big_url = MethodField()
|
|
|
|
is_fan = Field(attr="is_fan_attr")
|
|
|
|
def get_members(self, obj):
|
|
assert hasattr(obj, "members_attr"), "instance must have a members_attr attribute"
|
|
if obj.members_attr is None:
|
|
return []
|
|
|
|
return [m.get("id") for m in obj.members_attr if m["id"] is not None]
|
|
|
|
def get_i_am_member(self, obj):
|
|
assert hasattr(obj, "members_attr"), "instance must have a members_attr attribute"
|
|
if obj.members_attr is None:
|
|
return False
|
|
|
|
if "request" in self.context:
|
|
user = self.context["request"].user
|
|
user_ids = [m.get("id") for m in obj.members_attr if m["id"] is not None]
|
|
if not user.is_anonymous() and user.id in user_ids:
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_tags_colors(self, obj):
|
|
return dict(obj.tags_colors)
|
|
|
|
def get_my_permissions(self, obj):
|
|
if "request" in self.context:
|
|
user = self.context["request"].user
|
|
return calculate_permissions(
|
|
is_authenticated=user.is_authenticated(),
|
|
is_superuser=user.is_superuser,
|
|
is_member=self.get_i_am_member(obj),
|
|
is_admin=self.get_i_am_admin(obj),
|
|
role_permissions=obj.my_role_permissions_attr,
|
|
anon_permissions=obj.anon_permissions,
|
|
public_permissions=obj.public_permissions)
|
|
return []
|
|
|
|
def get_owner(self, obj):
|
|
return UserBasicInfoSerializer(obj.owner).data
|
|
|
|
def get_i_am_owner(self, obj):
|
|
if "request" in self.context:
|
|
return is_project_owner(self.context["request"].user, obj)
|
|
return False
|
|
|
|
def get_i_am_admin(self, obj):
|
|
if "request" in self.context:
|
|
return is_project_admin(self.context["request"].user, obj)
|
|
return False
|
|
|
|
def get_total_closed_milestones(self, obj):
|
|
assert hasattr(obj, "closed_milestones_attr"), "instance must have a closed_milestones_attr attribute"
|
|
return obj.closed_milestones_attr
|
|
|
|
def get_is_watcher(self, obj):
|
|
assert hasattr(obj, "notify_policies_attr"), "instance must have a notify_policies_attr attribute"
|
|
np = self.get_notify_level(obj)
|
|
return np is not None and np != NotifyLevel.none
|
|
|
|
def get_total_watchers(self, obj):
|
|
assert hasattr(obj, "notify_policies_attr"), "instance must have a notify_policies_attr attribute"
|
|
if obj.notify_policies_attr is None:
|
|
return 0
|
|
|
|
valid_notify_policies = [np for np in obj.notify_policies_attr if np["notify_level"] != NotifyLevel.none]
|
|
return len(valid_notify_policies)
|
|
|
|
def get_notify_level(self, obj):
|
|
assert hasattr(obj, "notify_policies_attr"), "instance must have a notify_policies_attr attribute"
|
|
if obj.notify_policies_attr is None:
|
|
return None
|
|
|
|
if "request" in self.context:
|
|
user = self.context["request"].user
|
|
for np in obj.notify_policies_attr:
|
|
if np["user_id"] == user.id:
|
|
return np["notify_level"]
|
|
|
|
return None
|
|
|
|
def get_logo_small_url(self, obj):
|
|
return services.get_logo_small_thumbnail_url(obj)
|
|
|
|
def get_logo_big_url(self, obj):
|
|
return services.get_logo_big_thumbnail_url(obj)
|
|
|
|
|
|
class ProjectDetailSerializer(ProjectSerializer):
|
|
us_statuses = Field(attr="userstory_statuses_attr")
|
|
points = Field(attr="points_attr")
|
|
task_statuses = Field(attr="task_statuses_attr")
|
|
issue_statuses = Field(attr="issue_statuses_attr")
|
|
issue_types = Field(attr="issue_types_attr")
|
|
priorities = Field(attr="priorities_attr")
|
|
severities = Field(attr="severities_attr")
|
|
userstory_custom_attributes = Field(attr="userstory_custom_attributes_attr")
|
|
task_custom_attributes = Field(attr="task_custom_attributes_attr")
|
|
issue_custom_attributes = Field(attr="issue_custom_attributes_attr")
|
|
roles = Field(attr="roles_attr")
|
|
members = MethodField()
|
|
total_memberships = MethodField()
|
|
is_out_of_owner_limits = MethodField()
|
|
|
|
# Admin fields
|
|
is_private_extra_info = MethodField()
|
|
max_memberships = MethodField()
|
|
issues_csv_uuid = Field()
|
|
tasks_csv_uuid = Field()
|
|
userstories_csv_uuid = Field()
|
|
transfer_token = Field()
|
|
|
|
def to_value(self, instance):
|
|
# Name attributes must be translated
|
|
for attr in ["userstory_statuses_attr", "points_attr", "task_statuses_attr",
|
|
"issue_statuses_attr", "issue_types_attr", "priorities_attr",
|
|
"severities_attr", "userstory_custom_attributes_attr",
|
|
"task_custom_attributes_attr", "issue_custom_attributes_attr", "roles_attr"]:
|
|
|
|
assert hasattr(instance, attr), "instance must have a {} attribute".format(attr)
|
|
val = getattr(instance, attr)
|
|
if val is None:
|
|
continue
|
|
|
|
for elem in val:
|
|
elem["name"] = _(elem["name"])
|
|
|
|
ret = super().to_value(instance)
|
|
|
|
admin_fields = [
|
|
"is_private_extra_info", "max_memberships", "issues_csv_uuid",
|
|
"tasks_csv_uuid", "userstories_csv_uuid", "transfer_token"
|
|
]
|
|
|
|
is_admin_user = False
|
|
if "request" in self.context:
|
|
user = self.context["request"].user
|
|
is_admin_user = permissions_services.is_project_admin(user, instance)
|
|
|
|
if not is_admin_user:
|
|
for admin_field in admin_fields:
|
|
del(ret[admin_field])
|
|
|
|
return ret
|
|
|
|
def get_members(self, obj):
|
|
assert hasattr(obj, "members_attr"), "instance must have a members_attr attribute"
|
|
if obj.members_attr is None:
|
|
return []
|
|
|
|
return MembershipDictSerializer([m for m in obj.members_attr if m['id'] is not None], many=True).data
|
|
|
|
def get_total_memberships(self, obj):
|
|
if obj.members_attr is None:
|
|
return 0
|
|
|
|
return len(obj.members_attr)
|
|
|
|
def get_is_out_of_owner_limits(self, obj):
|
|
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
|
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
|
return services.check_if_project_is_out_of_owner_limits(
|
|
obj,
|
|
current_memberships=self.get_total_memberships(obj),
|
|
current_private_projects=obj.private_projects_same_owner_attr,
|
|
current_public_projects=obj.public_projects_same_owner_attr
|
|
)
|
|
|
|
def get_is_private_extra_info(self, obj):
|
|
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
|
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
|
return services.check_if_project_privacity_can_be_changed(
|
|
obj,
|
|
current_memberships=self.get_total_memberships(obj),
|
|
current_private_projects=obj.private_projects_same_owner_attr,
|
|
current_public_projects=obj.public_projects_same_owner_attr
|
|
)
|
|
|
|
def get_max_memberships(self, obj):
|
|
return services.get_max_memberships_for_project(obj)
|
|
|
|
|
|
######################################################
|
|
# Project Templates
|
|
######################################################
|
|
|
|
class ProjectTemplateSerializer(serializers.LightSerializer):
|
|
id = Field()
|
|
name = I18NField()
|
|
slug = Field()
|
|
description = I18NField()
|
|
order = Field()
|
|
created_date = Field()
|
|
modified_date = Field()
|
|
default_owner_role = Field()
|
|
is_backlog_activated = Field()
|
|
is_kanban_activated = Field()
|
|
is_wiki_activated = Field()
|
|
is_issues_activated = Field()
|
|
videoconferences = Field()
|
|
videoconferences_extra_data = Field()
|
|
default_options = Field()
|
|
us_statuses = Field()
|
|
points = Field()
|
|
task_statuses = Field()
|
|
issue_statuses = Field()
|
|
issue_types = Field()
|
|
priorities = Field()
|
|
severities = Field()
|
|
roles = Field()
|