added flake, report, updated requirements

This commit is contained in:
2025-06-10 16:07:12 +02:00
parent 6ffd3ed533
commit 8778a088e6
55 changed files with 5633 additions and 54 deletions

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.contrib.auth.models import AbstractUser, Group
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
@@ -9,4 +9,3 @@ class User(AbstractUser):
def __str__(self):
return self.username

View File

@@ -75,6 +75,3 @@ class PasswordUpdateSerializer(ModelSerializer):
class Meta:
model = User
fields = ['old_password', 'new_password']

View File

@@ -1,14 +1,9 @@
from django.contrib.auth import update_session_auth_hash
from django.shortcuts import render
from django.utils.autoreload import raise_last_exception
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from django.core.exceptions import PermissionDenied
from authentication.models import User
from authentication.serializers import (UserSerializer,
UserUpdateSerializer,
UserRegisterSerializer,
@@ -45,7 +40,7 @@ class PasswordUpdateView(APIView):
serializer = PasswordUpdateSerializer(data=request.data)
if serializer.is_valid():
if not user.check_password(serializer.data.get("old_password")):
return Response({"old_password":"Wrong password"},
return Response({"old_password": "Wrong password"},
status=status.HTTP_400_BAD_REQUEST)
user.set_password(serializer.data.get('new_password'))
user.save()
@@ -56,6 +51,7 @@ class PasswordUpdateView(APIView):
return Response(response, status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UserView(APIView):
permission_classes = [IsAuthenticated]
@@ -86,4 +82,3 @@ class UserView(APIView):
raise PermissionDenied()
response = {"detail": "Username to delete must be given in data"}
return Response(response, status=status.HTTP_400_BAD_REQUEST)

View File

@@ -19,7 +19,8 @@ from django.urls import path, include
from authentication.views import (UserView, UserCreateView, PasswordUpdateView)
from support.views import ProjectViewSet, IssueViewSet, CommentViewSet
from rest_framework import routers
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
router = routers.SimpleRouter()
@@ -33,7 +34,13 @@ urlpatterns = [
path('api/', include(router.urls)),
path('api/user/', UserView.as_view(), name='user'),
path('api/user/create/', UserCreateView.as_view(), name='user_create'),
path('api/user/password-update/', PasswordUpdateView.as_view(), name='password_update'),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/user/password-update/',
PasswordUpdateView.as_view(),
name='password_update'),
path('api/token/',
TokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('api/token/refresh/',
TokenRefreshView.as_view(),
name='token_refresh'),
]

View File

@@ -10,7 +10,6 @@ class Project(models.Model):
IOS = 'iOS'
ANDROID = 'Android'
title = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
type = models.CharField(choices=Type.choices, max_length=10)
@@ -19,7 +18,6 @@ class Project(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
related_name='project_author', null=True)
contributors = models.ManyToManyField(settings.AUTH_USER_MODEL,
through='ProjectContributor',
related_name='contribution')
@@ -27,6 +25,7 @@ class Project(models.Model):
def __str__(self):
return self.title
class ProjectContributor(models.Model):
contributor = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
@@ -52,20 +51,17 @@ class Issue(models.Model):
LOW = 'Low'
MEDIUM = 'Medium'
HIGH = 'High'
class Status(models.TextChoices):
TODO = 'ToDo'
INPROGRESS = 'In Progress'
FINISHED = 'Finished'
class Tag(models.TextChoices):
BUG = 'Bug'
FEATURE = 'Feature'
TASK = 'Task'
title = models.CharField(max_length=255, verbose_name='title')
date_created = models.DateTimeField(auto_now_add=True)
description = models.TextField()
@@ -76,7 +72,8 @@ class Issue(models.Model):
on_delete=models.CASCADE)
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING,
related_name='issue_author', blank=True, null=True)
related_name='issue_author',
blank=True, null=True)
class Comment(models.Model):
@@ -87,4 +84,3 @@ class Comment(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING,
related_name='comment_author', null=True)

View File

@@ -1,6 +1,4 @@
from rest_framework.permissions import BasePermission
from support.models import Project, Issue, Comment
class IsAuthor(BasePermission):
@@ -18,4 +16,3 @@ class IsContributor(BasePermission):
return bool(request.user.is_authenticated
and request.user in object.contributors.all()
)

View File

@@ -28,15 +28,22 @@ class ProjectSerializer(ModelSerializer):
contributors = SlugRelatedField(many=True,
read_only='True',
slug_field='username')
class Meta:
model = Project
fields = ['id', 'author', 'contributors', 'title', 'type', 'date_created']
fields = ['id',
'author',
'contributors',
'title',
'type',
'date_created']
def validate_title(self, value):
if Project.objects.filter(title=value).exists():
raise ValidationError("Project already exists.")
return value
class ProjectDetailSerializer(ModelSerializer):
contributors = SlugRelatedField(many=True,
@@ -63,6 +70,7 @@ class IssueListSerializer(ModelSerializer):
model = Issue
fields = ['id', 'title']
class IssueSerializer(ModelSerializer):
author = StringRelatedField(many=False)
@@ -73,11 +81,9 @@ class IssueSerializer(ModelSerializer):
class IssueDetailSerializer(ModelSerializer):
comments = SerializerMethodField()
author = StringRelatedField(many=False)
class Meta:
model = Issue
fields = ['title', 'project', 'date_created', 'priority',

View File

@@ -4,10 +4,8 @@ from authentication.models import User
from support.serializers import (ProjectSerializer,
ProjectDetailSerializer,
ContributorSerializer,
ContributorListSerializer,
IssueSerializer,
IssueDetailSerializer,
IssueListSerializer,
CommentListSerializer,
CommentDetailSerializer)
from authentication.serializers import UserListSerializer
@@ -15,9 +13,9 @@ 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 support.permissions import IsContributor
from rest_framework.decorators import action
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.core.exceptions import PermissionDenied
class ProjectViewSet(ModelViewSet):
@@ -25,7 +23,6 @@ class ProjectViewSet(ModelViewSet):
serializer_class = ProjectSerializer
detail_serializer_class = ProjectDetailSerializer
def get_queryset(self):
"""
add a filter on contributor or author in querystring
@@ -35,25 +32,24 @@ class ProjectViewSet(ModelViewSet):
try:
user = User.objects.get(username=requested_contributor)
return Project.objects.filter(contributors=user)
except:
except User.DoesNotExist:
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:
except User.DoesNotExist:
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():
if request.user not in project.contributors.all():
raise PermissionDenied()
return Response(ProjectDetailSerializer(project).data)
@@ -65,7 +61,9 @@ class ProjectViewSet(ModelViewSet):
project = self.get_object()
if not request.user == project.author:
raise PermissionDenied()
serialized = ProjectDetailSerializer(project, data=request.data, partial=True)
serialized = ProjectDetailSerializer(project,
data=request.data,
partial=True)
if serialized.is_valid(raise_exception=True):
serialized.save()
return Response(serialized.data)
@@ -80,17 +78,18 @@ class ProjectViewSet(ModelViewSet):
@action(detail=True, methods=['patch'], permission_classes=[IsContributor])
def contributor(self, request, pk):
"""Add a contributor to a project
"""
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():
# check if requestor is contributor
if request.user not in Project.objects.get(id=pk).contributors.all():
raise PermissionDenied()
if request.data is None or not 'contributor' in request.data:
if request.data is None or 'contributor' not in request.data:
response = {"detail": "Key error;`contributor` is expected"}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
requested_contributor = request.data['contributor']
#get the user's instance
# get the user's instance
try:
user = User.objects.get(username=requested_contributor)
data = {'contributor': user.id, 'project': int(pk)}
@@ -98,12 +97,12 @@ class ProjectViewSet(ModelViewSet):
project = Project.objects.get(id=pk)
if serializer.is_valid():
serializer.save()
response = {"detail": f"User {user}"
response = {"detail": f"User {user}"
f"added to project ''{project}''"}
return Response(response, status=status.HTTP_202_ACCEPTED)
response = {"detail": "This user is already contributing"}
return Response(response, status=status.HTTP_226_IM_USED)
except:
except User.DoesNotExist:
response = {"detail": "User doesn't exist"}
return Response(response, status=status.HTTP_404_NOT_FOUND)
@@ -125,13 +124,13 @@ class IssueViewSet(ModelViewSet):
"""
if self.request.GET.get('project'):
project_id = int(self.request.GET.get('project'))
if not self.request.user in Project.objects.get(
if self.request.user not 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
# query on a list
return Issue.objects.filter(project__in=projects)
def perform_update(self, serializer):
@@ -167,7 +166,7 @@ class IssueViewSet(ModelViewSet):
raise PermissionDenied()
def create(self, request, *args, **kwargs):
if not 'project' in request.data:
if 'project' not in request.data:
return Response("A project id is required",
status=status.HTTP_400_BAD_REQUEST)
project = Project.objects.get(id=request.data['project'])
@@ -183,7 +182,7 @@ class IssueViewSet(ModelViewSet):
"detail": f"Issue {issue.id} created for project {project}",
"data": serializer.data
}
return Response(response, status = status.HTTP_201_CREATED)
return Response(response, status=status.HTTP_201_CREATED)
class CommentViewSet(ModelViewSet):
@@ -200,11 +199,12 @@ class CommentViewSet(ModelViewSet):
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():
if self.request.user not 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')
# 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)
@@ -226,4 +226,3 @@ class CommentViewSet(ModelViewSet):
return Response(response, status=status.HTTP_201_CREATED)
response = {"detail": f"{user} isn't contributor for '{project}'"}
return Response(response, status=status.HTTP_403_FORBIDDEN)