API performance
parent
1252a08dbb
commit
78a2118e8e
|
@ -1228,4 +1228,6 @@ class LightSerializer(serpy.Serializer):
|
|||
kwargs.pop("read_only", None)
|
||||
kwargs.pop("partial", None)
|
||||
kwargs.pop("files", None)
|
||||
context = kwargs.pop("context", {})
|
||||
super().__init__(*args, **kwargs)
|
||||
self.context = context
|
||||
|
|
|
@ -91,39 +91,55 @@ def _get_membership_permissions(membership):
|
|||
return []
|
||||
|
||||
|
||||
def calculate_permissions(is_authenticated=False, is_superuser=False, is_member=False,
|
||||
is_admin=False, role_permissions=[], anon_permissions=[],
|
||||
public_permissions=[]):
|
||||
if is_superuser:
|
||||
admins_permissions = list(map(lambda perm: perm[0], ADMINS_PERMISSIONS))
|
||||
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||
public_permissions = []
|
||||
anon_permissions = list(map(lambda perm: perm[0], ANON_PERMISSIONS))
|
||||
elif is_member:
|
||||
if is_admin:
|
||||
admins_permissions = list(map(lambda perm: perm[0], ADMINS_PERMISSIONS))
|
||||
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||
else:
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
members_permissions = members_permissions + role_permissions
|
||||
public_permissions = public_permissions if public_permissions is not None else []
|
||||
anon_permissions = anon_permissions if anon_permissions is not None else []
|
||||
elif is_authenticated:
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
public_permissions = public_permissions if public_permissions is not None else []
|
||||
anon_permissions = anon_permissions if anon_permissions is not None else []
|
||||
else:
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
public_permissions = []
|
||||
anon_permissions = anon_permissions if anon_permissions is not None else []
|
||||
|
||||
return set(admins_permissions + members_permissions + public_permissions + anon_permissions)
|
||||
|
||||
|
||||
def get_user_project_permissions(user, project, cache="user"):
|
||||
"""
|
||||
cache param determines how memberships are calculated trying to reuse the existing data
|
||||
in cache
|
||||
"""
|
||||
membership = _get_user_project_membership(user, project, cache=cache)
|
||||
if user.is_superuser:
|
||||
admins_permissions = list(map(lambda perm: perm[0], ADMINS_PERMISSIONS))
|
||||
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||
public_permissions = []
|
||||
anon_permissions = list(map(lambda perm: perm[0], ANON_PERMISSIONS))
|
||||
elif membership:
|
||||
if membership.is_admin:
|
||||
admins_permissions = list(map(lambda perm: perm[0], ADMINS_PERMISSIONS))
|
||||
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||
else:
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
members_permissions = members_permissions + _get_membership_permissions(membership)
|
||||
public_permissions = project.public_permissions if project.public_permissions is not None else []
|
||||
anon_permissions = project.anon_permissions if project.anon_permissions is not None else []
|
||||
elif user.is_authenticated():
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
public_permissions = project.public_permissions if project.public_permissions is not None else []
|
||||
anon_permissions = project.anon_permissions if project.anon_permissions is not None else []
|
||||
else:
|
||||
admins_permissions = []
|
||||
members_permissions = []
|
||||
public_permissions = []
|
||||
anon_permissions = project.anon_permissions if project.anon_permissions is not None else []
|
||||
|
||||
return set(admins_permissions + members_permissions + public_permissions + anon_permissions)
|
||||
is_member = membership is not None
|
||||
is_admin = is_member and membership.is_admin
|
||||
return calculate_permissions(
|
||||
is_authenticated = user.is_authenticated(),
|
||||
is_superuser = user.is_superuser,
|
||||
is_member = is_member,
|
||||
is_admin = is_admin,
|
||||
role_permissions = _get_membership_permissions(membership),
|
||||
anon_permissions = project.anon_permissions,
|
||||
public_permissions = project.public_permissions
|
||||
)
|
||||
|
||||
|
||||
def set_base_permissions_for_project(project):
|
||||
|
|
|
@ -61,7 +61,7 @@ from . import models
|
|||
from . import permissions
|
||||
from . import serializers
|
||||
from . import services
|
||||
|
||||
from . import utils as project_utils
|
||||
|
||||
######################################################
|
||||
## Project
|
||||
|
@ -70,11 +70,9 @@ from . import services
|
|||
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMixin, BlockeableDeleteMixin,
|
||||
TagsColorsResourceMixin, ModelCrudViewSet):
|
||||
queryset = models.Project.objects.all()
|
||||
serializer_class = serializers.ProjectDetailSerializer
|
||||
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
||||
list_serializer_class = serializers.ProjectSerializer
|
||||
permission_classes = (permissions.ProjectPermission, )
|
||||
filter_backends = (project_filters.QFilterBackend,
|
||||
filter_backends = (project_filters.UserOrderFilterBackend,
|
||||
project_filters.QFilterBackend,
|
||||
project_filters.CanViewProjectObjFilterBackend,
|
||||
project_filters.DiscoverModeFilterBackend)
|
||||
|
||||
|
@ -85,8 +83,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMix
|
|||
"is_kanban_activated")
|
||||
|
||||
ordering = ("name", "id")
|
||||
order_by_fields = ("memberships__user_order",
|
||||
"total_fans",
|
||||
order_by_fields = ("total_fans",
|
||||
"total_fans_last_week",
|
||||
"total_fans_last_month",
|
||||
"total_fans_last_year",
|
||||
|
@ -106,18 +103,8 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMix
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
||||
qs = qs.select_related("owner")
|
||||
# Prefetch doesn"t work correctly if then if the field is filtered later (it generates more queries)
|
||||
# so we add some custom prefetching
|
||||
qs = qs.prefetch_related("members")
|
||||
qs = qs.prefetch_related("memberships")
|
||||
qs = qs.prefetch_related(Prefetch("notify_policies",
|
||||
NotifyPolicy.objects.exclude(notify_level=NotifyLevel.none), to_attr="valid_notify_policies"))
|
||||
|
||||
Milestone = apps.get_model("milestones", "Milestone")
|
||||
qs = qs.prefetch_related(Prefetch("milestones",
|
||||
Milestone.objects.filter(closed=True), to_attr="closed_milestones"))
|
||||
qs = project_utils.attach_extra_info(qs, user=self.request.user)
|
||||
|
||||
# If filtering an activity period we must exclude the activities not updated recently enough
|
||||
now = timezone.now()
|
||||
|
@ -137,22 +124,20 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMix
|
|||
|
||||
return qs
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
|
||||
if self.action == "list":
|
||||
serializer_class = self.list_serializer_class
|
||||
elif self.action != "create":
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
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()
|
||||
self.lookup_field = "slug"
|
||||
|
||||
if permissions_services.is_project_admin(self.request.user, project):
|
||||
serializer_class = self.admin_serializer_class
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
return serializer_class
|
||||
def get_serializer_class(self):
|
||||
if self.action == "list":
|
||||
return serializers.LightProjectSerializer
|
||||
|
||||
if self.action in ["retrieve", "by_slug"]:
|
||||
return serializers.LightProjectDetailSerializer
|
||||
|
||||
return serializers.ProjectSerializer
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def change_logo(self, request, *args, **kwargs):
|
||||
|
@ -283,10 +268,9 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMix
|
|||
return response.Ok(data)
|
||||
|
||||
@list_route(methods=["GET"])
|
||||
def by_slug(self, request):
|
||||
def by_slug(self, request, *args, **kwargs):
|
||||
slug = request.QUERY_PARAMS.get("slug", None)
|
||||
project = get_object_or_404(models.Project, slug=slug)
|
||||
return self.retrieve(request, pk=project.pk)
|
||||
return self.retrieve(request, slug=slug)
|
||||
|
||||
@detail_route(methods=["GET", "PATCH"])
|
||||
def modules(self, request, pk=None):
|
||||
|
|
|
@ -45,7 +45,7 @@ class DiscoverModeFilterBackend(FilterBackend):
|
|||
if request.QUERY_PARAMS.get("is_featured", None) == 'true':
|
||||
qs = qs.order_by("?")
|
||||
|
||||
return super().filter_queryset(request, qs.distinct(), view)
|
||||
return super().filter_queryset(request, qs, view)
|
||||
|
||||
|
||||
class CanViewProjectObjFilterBackend(FilterBackend):
|
||||
|
@ -86,7 +86,7 @@ class CanViewProjectObjFilterBackend(FilterBackend):
|
|||
# external users / anonymous
|
||||
qs = qs.filter(anon_permissions__contains=["view_project"])
|
||||
|
||||
return super().filter_queryset(request, qs.distinct(), view)
|
||||
return super().filter_queryset(request, qs, view)
|
||||
|
||||
|
||||
class QFilterBackend(FilterBackend):
|
||||
|
@ -121,3 +121,34 @@ class QFilterBackend(FilterBackend):
|
|||
params=params,
|
||||
order_by=order_by)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserOrderFilterBackend(FilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
if request.user.is_anonymous():
|
||||
return queryset
|
||||
|
||||
raw_fieldname = request.QUERY_PARAMS.get(self.order_by_query_param, None)
|
||||
if not raw_fieldname:
|
||||
return queryset
|
||||
|
||||
if raw_fieldname.startswith("-"):
|
||||
field_name = raw_fieldname[1:]
|
||||
else:
|
||||
field_name = raw_fieldname
|
||||
|
||||
if field_name != "user_order":
|
||||
return queryset
|
||||
|
||||
model = queryset.model
|
||||
sql = """SELECT projects_membership.user_order
|
||||
FROM projects_membership
|
||||
WHERE
|
||||
projects_membership.project_id = {tbl}.id AND
|
||||
projects_membership.user_id = {user_id}
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table, user_id=request.user.id)
|
||||
queryset = queryset.extra(select={"user_order": sql})
|
||||
queryset = queryset.order_by(raw_fieldname)
|
||||
return queryset
|
||||
|
|
|
@ -144,7 +144,6 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
qs = qs.prefetch_related("attachments", "generated_user_stories")
|
||||
qs = qs.select_related("owner", "assigned_to", "status", "project")
|
||||
qs = self.attach_votes_attrs_to_queryset(qs)
|
||||
return self.attach_watchers_attrs_to_queryset(qs)
|
||||
|
|
|
@ -188,9 +188,10 @@ class WatchedModelMixin(object):
|
|||
|
||||
class BaseWatchedResourceModelSerializer(object):
|
||||
def get_is_watcher(self, obj):
|
||||
# The "is_watcher" attribute is attached in the get_queryset of the viewset.
|
||||
if "request" in self.context:
|
||||
user = self.context["request"].user
|
||||
return user.is_authenticated() and user.is_watcher(obj)
|
||||
return user.is_authenticated() and getattr(obj, "is_watcher", False)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -205,8 +206,8 @@ class WatchedResourceModelSerializer(BaseWatchedResourceModelSerializer, seriali
|
|||
|
||||
|
||||
class ListWatchedResourceModelSerializer(BaseWatchedResourceModelSerializer, serpy.Serializer):
|
||||
is_watcher = serializers.SerializerMethodField("get_is_watcher")
|
||||
total_watchers = serializers.SerializerMethodField("get_total_watchers")
|
||||
is_watcher = serpy.MethodField("get_is_watcher")
|
||||
total_watchers = serpy.MethodField("get_total_watchers")
|
||||
|
||||
|
||||
class EditableWatchedResourceModelSerializer(WatchedResourceModelSerializer):
|
||||
|
|
|
@ -25,12 +25,15 @@ from taiga.base.api import serializers
|
|||
from taiga.base.fields import JsonField
|
||||
from taiga.base.fields import PgArrayField
|
||||
|
||||
from taiga.permissions import services as permissions_services
|
||||
from taiga.users.services import get_photo_or_gravatar_url
|
||||
from taiga.users.serializers import UserBasicInfoSerializer
|
||||
from taiga.users.serializers import ProjectRoleSerializer
|
||||
from taiga.users.serializers import ListUserBasicInfoSerializer
|
||||
from taiga.users.validators import RoleExistsValidator
|
||||
|
||||
from taiga.permissions.services import get_user_project_permissions
|
||||
from taiga.permissions.services import calculate_permissions
|
||||
from taiga.permissions.services import is_project_admin, is_project_owner
|
||||
|
||||
from . import models
|
||||
|
@ -46,6 +49,7 @@ from .tagging.fields import TagsField
|
|||
from .tagging.fields import TagsColorsField
|
||||
from .validators import ProjectExistsValidator
|
||||
|
||||
import serpy
|
||||
|
||||
######################################################
|
||||
## Custom values for selectors
|
||||
|
@ -295,11 +299,6 @@ class ProjectSerializer(FanResourceSerializerMixin, WatchedResourceModelSerializ
|
|||
return False
|
||||
|
||||
def get_total_closed_milestones(self, obj):
|
||||
# The "closed_milestone" attribute can be attached in the get_queryset method of the viewset.
|
||||
qs_closed_milestones = getattr(obj, "closed_milestones", None)
|
||||
if qs_closed_milestones is not None:
|
||||
return len(qs_closed_milestones)
|
||||
|
||||
return obj.milestones.filter(closed=True).count()
|
||||
|
||||
def get_notify_level(self, obj):
|
||||
|
@ -310,11 +309,6 @@ class ProjectSerializer(FanResourceSerializerMixin, WatchedResourceModelSerializ
|
|||
return None
|
||||
|
||||
def get_total_watchers(self, obj):
|
||||
# The "valid_notify_policies" attribute can be attached in the get_queryset method of the viewset.
|
||||
qs_valid_notify_policies = getattr(obj, "valid_notify_policies", None)
|
||||
if qs_valid_notify_policies is not None:
|
||||
return len(qs_valid_notify_policies)
|
||||
|
||||
return obj.notify_policies.exclude(notify_level=NotifyLevel.none).count()
|
||||
|
||||
def get_logo_small_url(self, obj):
|
||||
|
@ -324,60 +318,253 @@ class ProjectSerializer(FanResourceSerializerMixin, WatchedResourceModelSerializ
|
|||
return services.get_logo_big_thumbnail_url(obj)
|
||||
|
||||
|
||||
class ProjectDetailSerializer(ProjectSerializer):
|
||||
us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories
|
||||
points = PointsSerializer(many=True, required=False)
|
||||
class LightProjectSerializer(serializers.LightSerializer):
|
||||
id = serpy.Field()
|
||||
name = serpy.Field()
|
||||
slug = serpy.Field()
|
||||
description = serpy.Field()
|
||||
created_date = serpy.Field()
|
||||
modified_date = serpy.Field()
|
||||
owner = serpy.MethodField()
|
||||
members = serpy.MethodField()
|
||||
total_milestones = serpy.Field()
|
||||
total_story_points = serpy.Field()
|
||||
is_backlog_activated = serpy.Field()
|
||||
is_kanban_activated = serpy.Field()
|
||||
is_wiki_activated = serpy.Field()
|
||||
is_issues_activated = serpy.Field()
|
||||
videoconferences = serpy.Field()
|
||||
videoconferences_extra_data = serpy.Field()
|
||||
creation_template = serpy.Field(attr="creation_template_id")
|
||||
is_private = serpy.Field()
|
||||
anon_permissions = serpy.Field()
|
||||
public_permissions = serpy.Field()
|
||||
is_featured = serpy.Field()
|
||||
is_looking_for_people = serpy.Field()
|
||||
looking_for_people_note = serpy.Field()
|
||||
blocked_code = serpy.Field()
|
||||
totals_updated_datetime = serpy.Field()
|
||||
total_fans = serpy.Field()
|
||||
total_fans_last_week = serpy.Field()
|
||||
total_fans_last_month = serpy.Field()
|
||||
total_fans_last_year = serpy.Field()
|
||||
total_activity = serpy.Field()
|
||||
total_activity_last_week = serpy.Field()
|
||||
total_activity_last_month = serpy.Field()
|
||||
total_activity_last_year = serpy.Field()
|
||||
|
||||
task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks
|
||||
tags = serpy.Field()
|
||||
tags_colors = serpy.MethodField()
|
||||
|
||||
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)
|
||||
default_points = serpy.Field(attr="default_points_id")
|
||||
default_us_status = serpy.Field(attr="default_us_status_id")
|
||||
default_task_status = serpy.Field(attr="default_task_status_id")
|
||||
default_priority = serpy.Field(attr="default_priority_id")
|
||||
default_severity = serpy.Field(attr="default_severity_id")
|
||||
default_issue_status = serpy.Field(attr="default_issue_status_id")
|
||||
default_issue_type = serpy.Field(attr="default_issue_type_id")
|
||||
|
||||
userstory_custom_attributes = UserStoryCustomAttributeSerializer(source="userstorycustomattributes",
|
||||
many=True, required=False)
|
||||
task_custom_attributes = TaskCustomAttributeSerializer(source="taskcustomattributes",
|
||||
many=True, required=False)
|
||||
issue_custom_attributes = IssueCustomAttributeSerializer(source="issuecustomattributes",
|
||||
many=True, required=False)
|
||||
my_permissions = serpy.MethodField()
|
||||
|
||||
roles = ProjectRoleSerializer(source="roles", many=True, read_only=True)
|
||||
members = serializers.SerializerMethodField(method_name="get_members")
|
||||
total_memberships = serializers.SerializerMethodField(method_name="get_total_memberships")
|
||||
is_out_of_owner_limits = serializers.SerializerMethodField(method_name="get_is_out_of_owner_limits")
|
||||
i_am_owner = serpy.MethodField()
|
||||
i_am_admin = serpy.MethodField()
|
||||
i_am_member = serpy.MethodField()
|
||||
|
||||
notify_level = serpy.MethodField("get_notify_level")
|
||||
total_closed_milestones = serpy.MethodField()
|
||||
|
||||
is_watcher = serpy.MethodField()
|
||||
total_watchers = serpy.MethodField()
|
||||
|
||||
logo_small_url = serpy.MethodField()
|
||||
logo_big_url = serpy.MethodField()
|
||||
|
||||
is_fan = serpy.Field(attr="is_fan_attr")
|
||||
|
||||
def get_members(self, obj):
|
||||
qs = obj.memberships.filter(user__isnull=False)
|
||||
qs = qs.extra(select={"complete_user_name":"concat(full_name, username)"})
|
||||
qs = qs.order_by("complete_user_name")
|
||||
qs = qs.select_related("role", "user")
|
||||
serializer = ProjectMemberSerializer(qs, many=True)
|
||||
return serializer.data
|
||||
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
|
||||
if not user.is_anonymous() and user.id in [m.get("id") for m in obj.members_attr if m["id"] is not None]:
|
||||
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 ListUserBasicInfoSerializer(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 != 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 LightProjectDetailSerializer(LightProjectSerializer):
|
||||
us_statuses = serpy.Field(attr="userstory_statuses_attr")
|
||||
points = serpy.Field(attr="points_attr")
|
||||
task_statuses = serpy.Field(attr="task_statuses_attr")
|
||||
issue_statuses = serpy.Field(attr="issue_statuses_attr")
|
||||
issue_types = serpy.Field(attr="issue_types_attr")
|
||||
priorities = serpy.Field(attr="priorities_attr")
|
||||
severities = serpy.Field(attr="severities_attr")
|
||||
userstory_custom_attributes = serpy.Field(attr="userstory_custom_attributes_attr")
|
||||
task_custom_attributes = serpy.Field(attr="task_custom_attributes_attr")
|
||||
issue_custom_attributes = serpy.Field(attr="issue_custom_attributes_attr")
|
||||
roles = serpy.Field(attr="roles_attr")
|
||||
members = serpy.MethodField()
|
||||
total_memberships = serpy.MethodField()
|
||||
is_out_of_owner_limits = serpy.MethodField()
|
||||
|
||||
#Admin fields
|
||||
is_private_extra_info = serpy.MethodField()
|
||||
max_memberships = serpy.MethodField()
|
||||
issues_csv_uuid = serpy.Field()
|
||||
tasks_csv_uuid = serpy.Field()
|
||||
userstories_csv_uuid = serpy.Field()
|
||||
transfer_token = serpy.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 []
|
||||
|
||||
ret = []
|
||||
for m in obj.members_attr:
|
||||
m["full_name_display"] = m["full_name"] or m["username"] or m["email"]
|
||||
del(m["email"])
|
||||
del(m["complete_user_name"])
|
||||
if not m["id"] is None:
|
||||
ret.append(m)
|
||||
|
||||
return ret
|
||||
|
||||
def get_total_memberships(self, obj):
|
||||
return services.get_total_project_memberships(obj)
|
||||
if obj.members_attr is None:
|
||||
return 0
|
||||
|
||||
return len(obj.members_attr)
|
||||
|
||||
def get_is_out_of_owner_limits(self, obj):
|
||||
return services.check_if_project_is_out_of_owner_limits(obj)
|
||||
|
||||
|
||||
class ProjectDetailAdminSerializer(ProjectDetailSerializer):
|
||||
is_private_extra_info = serializers.SerializerMethodField(method_name="get_is_private_extra_info")
|
||||
max_memberships = serializers.SerializerMethodField(method_name="get_max_memberships")
|
||||
|
||||
class Meta:
|
||||
model = models.Project
|
||||
read_only_fields = ("created_date", "modified_date", "slug", "blocked_code")
|
||||
exclude = ("logo", "last_us_ref", "last_task_ref", "last_issue_ref")
|
||||
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):
|
||||
return services.check_if_project_privacity_can_be_changed(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)
|
||||
|
||||
|
||||
######################################################
|
||||
## Liked
|
||||
######################################################
|
||||
|
|
|
@ -27,30 +27,45 @@ ERROR_MAX_PUBLIC_PROJECTS = 'max_public_projects'
|
|||
ERROR_MAX_PRIVATE_PROJECTS = 'max_private_projects'
|
||||
ERROR_PROJECT_WITHOUT_OWNER = 'project_without_owner'
|
||||
|
||||
def check_if_project_privacity_can_be_changed(project):
|
||||
def check_if_project_privacity_can_be_changed(project,
|
||||
current_memberships=None,
|
||||
current_private_projects=None,
|
||||
current_public_projects=None):
|
||||
"""Return if the project privacity can be changed from private to public or viceversa.
|
||||
|
||||
:param project: A project object.
|
||||
:param current_memberships: Project total memberships, If None it will be calculated.
|
||||
:param current_private_projects: total private projects owned by the project owner, If None it will be calculated.
|
||||
:param current_public_projects: total public projects owned by the project owner, If None it will be calculated.
|
||||
|
||||
:return: A dict like this {'can_be_updated': bool, 'reason': error message}.
|
||||
"""
|
||||
if project.owner is None:
|
||||
return {'can_be_updated': False, 'reason': ERROR_PROJECT_WITHOUT_OWNER}
|
||||
|
||||
if project.is_private:
|
||||
if current_memberships is None:
|
||||
current_memberships = project.memberships.count()
|
||||
|
||||
if project.is_private:
|
||||
max_memberships = project.owner.max_memberships_public_projects
|
||||
error_memberships_exceeded = ERROR_MAX_PUBLIC_PROJECTS_MEMBERSHIPS
|
||||
|
||||
if current_public_projects is None:
|
||||
current_projects = project.owner.owned_projects.filter(is_private=False).count()
|
||||
else:
|
||||
current_projects = current_public_projects
|
||||
|
||||
max_projects = project.owner.max_public_projects
|
||||
error_project_exceeded = ERROR_MAX_PUBLIC_PROJECTS
|
||||
else:
|
||||
current_memberships = project.memberships.count()
|
||||
max_memberships = project.owner.max_memberships_private_projects
|
||||
error_memberships_exceeded = ERROR_MAX_PRIVATE_PROJECTS_MEMBERSHIPS
|
||||
|
||||
if current_private_projects is None:
|
||||
current_projects = project.owner.owned_projects.filter(is_private=True).count()
|
||||
else:
|
||||
current_projects = current_private_projects
|
||||
|
||||
max_projects = project.owner.max_private_projects
|
||||
error_project_exceeded = ERROR_MAX_PRIVATE_PROJECTS
|
||||
|
||||
|
@ -139,25 +154,43 @@ def check_if_project_can_be_transfered(project, new_owner):
|
|||
return (True, None)
|
||||
|
||||
|
||||
def check_if_project_is_out_of_owner_limits(project):
|
||||
def check_if_project_is_out_of_owner_limits(project,
|
||||
current_memberships=None,
|
||||
current_private_projects=None,
|
||||
current_public_projects=None):
|
||||
|
||||
"""Return if the project fits on its owner limits.
|
||||
|
||||
:param project: A project object.
|
||||
:param current_memberships: Project total memberships, If None it will be calculated.
|
||||
:param current_private_projects: total private projects owned by the project owner, If None it will be calculated.
|
||||
:param current_public_projects: total public projects owned by the project owner, If None it will be calculated.
|
||||
|
||||
:return: bool
|
||||
"""
|
||||
if project.owner is None:
|
||||
return {'can_be_updated': False, 'reason': ERROR_PROJECT_WITHOUT_OWNER}
|
||||
|
||||
if project.is_private:
|
||||
if current_memberships is None:
|
||||
current_memberships = project.memberships.count()
|
||||
|
||||
if project.is_private:
|
||||
max_memberships = project.owner.max_memberships_private_projects
|
||||
|
||||
if current_private_projects is None:
|
||||
current_projects = project.owner.owned_projects.filter(is_private=True).count()
|
||||
else:
|
||||
current_projects = current_private_projects
|
||||
|
||||
max_projects = project.owner.max_private_projects
|
||||
else:
|
||||
current_memberships = project.memberships.count()
|
||||
max_memberships = project.owner.max_memberships_public_projects
|
||||
|
||||
if current_public_projects is None:
|
||||
current_projects = project.owner.owned_projects.filter(is_private=False).count()
|
||||
else:
|
||||
current_projects = current_public_projects
|
||||
|
||||
max_projects = project.owner.max_public_projects
|
||||
|
||||
if max_memberships is not None and current_memberships > max_memberships:
|
||||
|
|
|
@ -0,0 +1,436 @@
|
|||
# -*- 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>
|
||||
# Copyright (C) 2014-2016 Anler Hernández <hello@anler.me>
|
||||
# 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/>.
|
||||
|
||||
def attach_members(queryset, as_field="members_attr"):
|
||||
"""Attach a json members representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the members as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(t))
|
||||
FROM(
|
||||
SELECT
|
||||
users_user.id,
|
||||
users_user.username,
|
||||
users_user.full_name,
|
||||
users_user.email,
|
||||
concat(full_name, username) complete_user_name,
|
||||
users_user.color,
|
||||
users_user.photo,
|
||||
users_user.is_active,
|
||||
users_role.name role_name
|
||||
|
||||
FROM projects_membership
|
||||
LEFT JOIN users_user ON projects_membership.user_id = users_user.id
|
||||
LEFT JOIN users_role ON users_role.id = projects_membership.role_id
|
||||
WHERE projects_membership.project_id = {tbl}.id
|
||||
ORDER BY complete_user_name) t"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_closed_milestones(queryset, as_field="closed_milestones_attr"):
|
||||
"""Attach a closed milestones counter to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the counter as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT COUNT(milestones_milestone.id)
|
||||
FROM milestones_milestone
|
||||
WHERE
|
||||
milestones_milestone.project_id = {tbl}.id AND
|
||||
milestones_milestone.closed = True
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_notify_policies(queryset, as_field="notify_policies_attr"):
|
||||
"""Attach a json notification policies representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the notification policies as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(notifications_notifypolicy))
|
||||
FROM notifications_notifypolicy
|
||||
WHERE
|
||||
notifications_notifypolicy.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_userstory_statuses(queryset, as_field="userstory_statuses_attr"):
|
||||
"""Attach a json userstory statuses representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the userstory statuses as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_userstorystatus))
|
||||
FROM projects_userstorystatus
|
||||
WHERE
|
||||
projects_userstorystatus.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_points(queryset, as_field="points_attr"):
|
||||
"""Attach a json points representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the points as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_points))
|
||||
FROM projects_points
|
||||
WHERE
|
||||
projects_points.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_task_statuses(queryset, as_field="task_statuses_attr"):
|
||||
"""Attach a json task statuses representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the task statuses as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_taskstatus))
|
||||
FROM projects_taskstatus
|
||||
WHERE
|
||||
projects_taskstatus.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_issue_statuses(queryset, as_field="issue_statuses_attr"):
|
||||
"""Attach a json issue statuses representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the statuses as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_issuestatus))
|
||||
FROM projects_issuestatus
|
||||
WHERE
|
||||
projects_issuestatus.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_issue_types(queryset, as_field="issue_types_attr"):
|
||||
"""Attach a json issue types representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the types as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_issuetype))
|
||||
FROM projects_issuetype
|
||||
WHERE
|
||||
projects_issuetype.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_priorities(queryset, as_field="priorities_attr"):
|
||||
"""Attach a json priorities representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the priorities as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_priority))
|
||||
FROM projects_priority
|
||||
WHERE
|
||||
projects_priority.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_severities(queryset, as_field="severities_attr"):
|
||||
"""Attach a json severities representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the severities as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(projects_severity))
|
||||
FROM projects_severity
|
||||
WHERE
|
||||
projects_severity.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_userstory_custom_attributes(queryset, as_field="userstory_custom_attributes_attr"):
|
||||
"""Attach a json userstory custom attributes representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the userstory custom attributes as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(custom_attributes_userstorycustomattribute))
|
||||
FROM custom_attributes_userstorycustomattribute
|
||||
WHERE
|
||||
custom_attributes_userstorycustomattribute.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_task_custom_attributes(queryset, as_field="task_custom_attributes_attr"):
|
||||
"""Attach a json task custom attributes representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the task custom attributes as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(custom_attributes_taskcustomattribute))
|
||||
FROM custom_attributes_taskcustomattribute
|
||||
WHERE
|
||||
custom_attributes_taskcustomattribute.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_issue_custom_attributes(queryset, as_field="issue_custom_attributes_attr"):
|
||||
"""Attach a json issue custom attributes representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the issue custom attributes as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(custom_attributes_issuecustomattribute))
|
||||
FROM custom_attributes_issuecustomattribute
|
||||
WHERE
|
||||
custom_attributes_issuecustomattribute.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_roles(queryset, as_field="roles_attr"):
|
||||
"""Attach a json roles representation to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the roles as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
sql = """SELECT json_agg(row_to_json(users_role))
|
||||
FROM users_role
|
||||
WHERE
|
||||
users_role.project_id = {tbl}.id
|
||||
"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table)
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_is_fan(queryset, user, as_field="is_fan_attr"):
|
||||
"""Attach a is fan boolean to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the boolean as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
if user is None or user.is_anonymous():
|
||||
sql = """SELECT false"""
|
||||
else:
|
||||
sql = """SELECT COUNT(likes_like.id) > 0
|
||||
FROM likes_like
|
||||
INNER JOIN django_content_type
|
||||
ON likes_like.content_type_id = django_content_type.id
|
||||
WHERE
|
||||
django_content_type.model = 'project' AND
|
||||
django_content_type.app_label = 'projects' AND
|
||||
likes_like.user_id = {user_id} AND
|
||||
likes_like.object_id = {tbl}.id"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table, user_id=user.id)
|
||||
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_my_role_permissions(queryset, user, as_field="my_role_permissions_attr"):
|
||||
"""Attach a permission array to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the permissions as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
if user is None or user.is_anonymous():
|
||||
sql = """SELECT '{}'"""
|
||||
else:
|
||||
sql = """SELECT users_role.permissions
|
||||
FROM projects_membership
|
||||
LEFT JOIN users_user ON projects_membership.user_id = users_user.id
|
||||
LEFT JOIN users_role ON users_role.id = projects_membership.role_id
|
||||
WHERE
|
||||
projects_membership.project_id = {tbl}.id AND
|
||||
users_user.id = {user_id}"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table, user_id=user.id)
|
||||
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_private_projects_same_owner(queryset, user, as_field="private_projects_same_owner_attr"):
|
||||
"""Attach a private projects counter to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the counter as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
if user is None or user.is_anonymous():
|
||||
sql = """SELECT '0'"""
|
||||
else:
|
||||
sql = """SELECT COUNT(id)
|
||||
FROM projects_project p_aux
|
||||
WHERE
|
||||
p_aux.is_private = True AND
|
||||
p_aux.owner_id = {tbl}.owner_id"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table, user_id=user.id)
|
||||
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_public_projects_same_owner(queryset, user, as_field="public_projects_same_owner_attr"):
|
||||
"""Attach a public projects counter to each object of the queryset.
|
||||
|
||||
:param queryset: A Django projects queryset object.
|
||||
:param as_field: Attach the counter as an attribute with this name.
|
||||
|
||||
:return: Queryset object with the additional `as_field` field.
|
||||
"""
|
||||
model = queryset.model
|
||||
if user is None or user.is_anonymous():
|
||||
sql = """SELECT '0'"""
|
||||
else:
|
||||
sql = """SELECT COUNT(id)
|
||||
FROM projects_project p_aux
|
||||
WHERE
|
||||
p_aux.is_private = False AND
|
||||
p_aux.owner_id = {tbl}.owner_id"""
|
||||
|
||||
sql = sql.format(tbl=model._meta.db_table, user_id=user.id)
|
||||
|
||||
queryset = queryset.extra(select={as_field: sql})
|
||||
return queryset
|
||||
|
||||
|
||||
def attach_extra_info(queryset, user=None):
|
||||
queryset = attach_members(queryset)
|
||||
queryset = attach_closed_milestones(queryset)
|
||||
queryset = attach_notify_policies(queryset)
|
||||
queryset = attach_userstory_statuses(queryset)
|
||||
queryset = attach_points(queryset)
|
||||
queryset = attach_task_statuses(queryset)
|
||||
queryset = attach_issue_statuses(queryset)
|
||||
queryset = attach_issue_types(queryset)
|
||||
queryset = attach_priorities(queryset)
|
||||
queryset = attach_severities(queryset)
|
||||
queryset = attach_userstory_custom_attributes(queryset)
|
||||
queryset = attach_task_custom_attributes(queryset)
|
||||
queryset = attach_issue_custom_attributes(queryset)
|
||||
queryset = attach_roles(queryset)
|
||||
queryset = attach_is_fan(queryset, user)
|
||||
queryset = attach_my_role_permissions(queryset, user)
|
||||
queryset = attach_private_projects_same_owner(queryset, user)
|
||||
queryset = attach_public_projects_same_owner(queryset, user)
|
||||
|
||||
return queryset
|
|
@ -198,7 +198,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||
|
||||
def _fill_cached_memberships(self):
|
||||
self._cached_memberships = {}
|
||||
qs = self.memberships.prefetch_related("user", "project", "role")
|
||||
qs = self.memberships.select_related("user", "project", "role")
|
||||
for membership in qs.all():
|
||||
self._cached_memberships[membership.project.id] = membership
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.apps import apps
|
|||
|
||||
from taiga.base.utils import json
|
||||
from taiga.projects import choices as project_choices
|
||||
from taiga.projects.serializers import ProjectDetailSerializer
|
||||
from taiga.projects.serializers import ProjectSerializer
|
||||
from taiga.permissions.choices import MEMBERS_PERMISSIONS
|
||||
|
||||
from tests import factories as f
|
||||
|
@ -153,12 +153,12 @@ def test_project_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
project_data = ProjectDetailSerializer(data.private_project2).data
|
||||
project_data = ProjectSerializer(data.private_project2).data
|
||||
project_data["is_private"] = False
|
||||
results = helper_test_http_method(client, 'put', url, json.dumps(project_data), users)
|
||||
assert results == [401, 403, 403, 200]
|
||||
|
||||
project_data = ProjectDetailSerializer(data.blocked_project).data
|
||||
project_data = ProjectSerializer(data.blocked_project).data
|
||||
project_data["is_private"] = False
|
||||
results = helper_test_http_method(client, 'put', blocked_url, json.dumps(project_data), users)
|
||||
assert results == [401, 403, 403, 451]
|
||||
|
|
|
@ -625,7 +625,7 @@ def test_projects_user_order(client):
|
|||
|
||||
#Testing user order
|
||||
url = reverse("projects-list")
|
||||
url = "%s?member=%s&order_by=memberships__user_order" % (url, user.id)
|
||||
url = "%s?member=%s&order_by=user_order" % (url, user.id)
|
||||
response = client.json.get(url)
|
||||
response_content = response.data
|
||||
assert response.status_code == 200
|
||||
|
|
Loading…
Reference in New Issue