from rest_framework.viewsets import ModelViewSet from support.models import Project, ProjectContributor, Issue, Comment from authentication.models import User from support.serializers import (ProjectSerializer, ProjectDetailSerializer, ContributorSerializer, ContributorListSerializer, IssueSerializer, IssueDetailSerializer, IssueListSerializer, CommentListSerializer, CommentDetailSerializer) from authentication.serializers import UserListSerializer from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import (IsAuthenticated, IsAuthenticatedOrReadOnly) from support.permissions import IsAuthor, IsContributor from rest_framework.decorators import action from django.core.exceptions import PermissionDenied, ObjectDoesNotExist class ProjectViewSet(ModelViewSet): permission_classes = [IsAuthenticatedOrReadOnly] serializer_class = ProjectSerializer detail_serializer_class = ProjectDetailSerializer def get_queryset(self): """ add a filter on contributor or author in querystring """ if self.request.GET.get('contributor'): requested_contributor = self.request.GET.get('contributor') try: user = User.objects.get(username=requested_contributor) return Project.objects.filter(contributors=user) except: return User.objects.filter(username=requested_contributor) if self.request.GET.get('author'): requested_author = self.request.GET.get('author') try: user = User.objects.get(username=requested_author) return Project.objects.filter(author=user) except: return User.objects.filter(username=requested_author) return Project.objects.filter(active=True) def retrieve(self, request, *args, **kwargs): """ check if requestor is in the project's contributor Raises exception or returns project detail """ project = self.get_object() if not request.user in project.contributors.all(): raise PermissionDenied() return Response(ProjectDetailSerializer(project).data) def partial_update(self, request, *args, **kwargs): """ check if requestor is author then save changes and returns project details """ project = self.get_object() if not request.user == project.author: raise PermissionDenied() serialized = ProjectDetailSerializer(project, data=request.data, partial=True) if serialized.is_valid(raise_exception=True): serialized.save() return Response(serialized.data) def perform_create(self, serializer): """set authenticated user as author and contributor on creation""" test = serializer.save(author=self.request.user) data = {'contributor': self.request.user.id, 'project': test.id} contributor_serializer = ContributorSerializer(data=data) if contributor_serializer.is_valid(): contributor_serializer.save() @action(detail=True, methods=['patch'], permission_classes=[IsContributor]) def contributor(self, request, pk): """Add a contributor to a project by creating a ProjectContributor's instance """ #check if requestor is contributor if not request.user in Project.objects.get(id=pk).contributors.all(): raise PermissionDenied() if request.data is None or not 'contributor' in request.data: return Response(f"Key error;`contributor` is expected", status=status.HTTP_400_BAD_REQUEST) #get the user's instance contributor = User.objects.get(username=request.data['contributor']) data = {'contributor': contributor.id, 'project': int(pk)} serializer = ContributorSerializer(data=data) project = Project.objects.get(id=pk) if serializer.is_valid(): serializer.save() return Response(f"User {contributor} " f"added to project {project}", status=status.HTTP_202_ACCEPTED) response = {'message': 'This user is already contributing'} return Response(response, status=status.HTTP_226_IM_USED) class IssueViewSet(ModelViewSet): permission_classes = [IsAuthenticated] serializer_class = IssueSerializer detail_serializer_class = IssueDetailSerializer def get_serializer_class(self): if self.action == 'retrieve': return self.detail_serializer_class return super().get_serializer_class() def get_queryset(self): """ returns only the issues related to projects where requestor is contributor or empty list """ if self.request.GET.get('project'): project_id = int(self.request.GET.get('project')) if not self.request.user in Project.objects.get( id=project_id).contributors.all(): raise PermissionDenied() return Issue.objects.filter(project=project_id) projects = Project.objects.filter(contributors=self.request.user).values('id') #query on a list return Issue.objects.filter(project__in=projects) def perform_update(self, serializer): """ Check if requestor is author allows him to partial update change the author to assign issue """ issue = self.get_object() if not self.request.user == issue.author: raise PermissionDenied() if serializer.is_valid(raise_exception=True): if self.request.data['author']: requested_author = User.objects.get( username=self.request.data['author']) serializer.save(author=requested_author) return Response(serializer.data) return Response("Data error", status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['get']) def contributors(self, request, pk): """ Check if requestor is contributor then returns the list of the contributors to the issue's project or raise unauthorized """ issue = Issue.objects.get(id=pk) if ProjectContributor.objects.filter(project=issue.project).filter(contributor=request.user): return Response(UserListSerializer(issue.project.contributors.all(), many=True).data) else: raise PermissionDenied() def create(self, request, *args, **kwargs): if not 'project' in request.data: return Response("A project id is required", status=status.HTTP_400_BAD_REQUEST) project = Project.objects.get(id=request.data['project']) serializer = IssueSerializer(data=request.data) if self.request.user not in project.contributors.all(): return Response("Requestor isn't contributor for this project", status=status.HTTP_403_FORBIDDEN) if serializer.is_valid(raise_exception=True): serializer.save(author=self.request.user) response = { "message": f"Issue created for project {project}", "data": serializer.data } return Response(response, status = status.HTTP_201_CREATED) class CommentViewSet(ModelViewSet): permission_classes = [IsAuthenticated] serializer_class = CommentListSerializer detail_serializer_class = CommentDetailSerializer queryset = Comment.objects.all() def get_queryset(self): """ Returns only comments associated with issue where the requestor is project's contributor """ if self.request.GET.get('issue'): issue_id = int(self.request.GET.get('issue')) project = Issue.objects.get(id=issue_id).project if not self.request.user in project.contributors.all(): raise PermissionDenied() return Comment.objects.filter(issue=issue_id) #or returns those from projects where requestor is contributing projects = Project.objects.filter(contributors=self.request.user).values('id') issues = Issue.objects.filter(project__in=projects) return Comment.objects.filter(issue__in=issues) def get_serializer_class(self): if self.action == 'retrieve': return self.detail_serializer_class return super().get_serializer_class() def create(self, request, *args, **kwargs): user = User.objects.get(username=request.user) issue = Issue.objects.get(id=request.data['issue']) project = issue.project if issue.project.contributors.filter(username=request.user.username): serializer = CommentDetailSerializer(data=request.data) if serializer.is_valid(raise_exception=True): serializer.save(author=user) response = {"message": "comment created", "data": serializer.data} return Response(response, status=status.HTTP_201_CREATED) return Response("Not allowed; " f"{user} isn't contributor for project {project}", status=status.HTTP_403_FORBIDDEN)