working on permission is_contributor; checkpoint
This commit is contained in:
parent
776ba21695
commit
3636d4a72b
@ -1,4 +1,4 @@
|
|||||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField, ValidationError
|
from rest_framework.serializers import ModelSerializer, ValidationError
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from authentication.models import User
|
from authentication.models import User
|
||||||
|
|
||||||
@ -50,7 +50,6 @@ class UserRegisterSerializer(ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Create and return a new `User` instance, given the validated data.
|
Create and return a new `User` instance, given the validated data.
|
||||||
"""
|
"""
|
||||||
#if self.validate(validated_data):
|
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username=validated_data['username'],
|
username=validated_data['username'],
|
||||||
email=validated_data['email'],
|
email=validated_data['email'],
|
||||||
|
@ -133,6 +133,6 @@ REST_FRAMEWORK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=10),
|
'ACCESS_TOKEN_LIFETIME': timedelta(days=30),
|
||||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
|
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ Including another URLconf
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from authentication.views import (UserView, UserCreateView, PasswordUpdateView)
|
from authentication.views import (UserView, UserCreateView, PasswordUpdateView)
|
||||||
from support.views import ProjectViewSet
|
from support.views import ProjectViewSet, IssueViewSet, CommentViewSet, ContributorViewSet
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||||
|
|
||||||
@ -25,6 +25,9 @@ from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
|||||||
router = routers.SimpleRouter()
|
router = routers.SimpleRouter()
|
||||||
#router.register('user', UserViewSet, basename='user')
|
#router.register('user', UserViewSet, basename='user')
|
||||||
router.register('project', ProjectViewSet, basename='project')
|
router.register('project', ProjectViewSet, basename='project')
|
||||||
|
router.register('issue', IssueViewSet, basename='issue')
|
||||||
|
router.register('comment', CommentViewSet, basename='comment')
|
||||||
|
router.register('contributors', ContributorViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
@ -2,12 +2,20 @@ from django.contrib import admin
|
|||||||
from support.models import Project, Issue, Comment, ProjectContributor
|
from support.models import Project, Issue, Comment, ProjectContributor
|
||||||
from authentication.models import User
|
from authentication.models import User
|
||||||
|
|
||||||
class AdminUser:
|
class AdminProject(admin.ModelAdmin):
|
||||||
pass
|
list_display = ('id', 'title', 'author', 'contributors')
|
||||||
|
|
||||||
|
@admin.display(description='contributors')
|
||||||
|
def contributors(self, obj):
|
||||||
|
return obj.contributor
|
||||||
|
|
||||||
|
class AdminIssue(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'title', 'author', 'project')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(User)
|
admin.site.register(User)
|
||||||
admin.site.register(Project)
|
admin.site.register(Project, AdminProject)
|
||||||
admin.site.register(Issue)
|
admin.site.register(Issue, AdminIssue)
|
||||||
admin.site.register(Comment)
|
admin.site.register(Comment)
|
||||||
admin.site.register(ProjectContributor)
|
admin.site.register(ProjectContributor)
|
||||||
|
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-26 18:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('support', '0010_alter_comment_author_alter_issue_author_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='priority',
|
||||||
|
field=models.CharField(choices=[('L', 'Low'), ('M', 'Medium'), ('H', 'High')], max_length=15),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('ToDo', 'Todo'), ('InProgress', 'Inprogress'), ('Finished', 'Finished')], max_length=15),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='tag',
|
||||||
|
field=models.CharField(choices=[('Bug', 'Bug'), ('Feature', 'Feature'), ('Task', 'Task')], max_length=15),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-26 18:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('support', '0011_alter_issue_priority_alter_issue_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='priority',
|
||||||
|
field=models.CharField(choices=[('Low', 'Low'), ('Medium', 'Medium'), ('High', 'High')], max_length=15),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('ToDo', 'Todo'), ('In Progress', 'Inprogress'), ('Finished', 'Finished')], max_length=15),
|
||||||
|
),
|
||||||
|
]
|
19
softdesk/support/migrations/0013_alter_issue_project.py
Normal file
19
softdesk/support/migrations/0013_alter_issue_project.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-26 19:06
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('support', '0012_alter_issue_priority_alter_issue_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='project',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='support.project'),
|
||||||
|
),
|
||||||
|
]
|
19
softdesk/support/migrations/0014_alter_issue_project.py
Normal file
19
softdesk/support/migrations/0014_alter_issue_project.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-27 09:30
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('support', '0013_alter_issue_project'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issue',
|
||||||
|
name='project',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='support.project'),
|
||||||
|
),
|
||||||
|
]
|
@ -45,14 +45,14 @@ class ProjectContributor(models.Model):
|
|||||||
class Issue(models.Model):
|
class Issue(models.Model):
|
||||||
|
|
||||||
class Priority(models.TextChoices):
|
class Priority(models.TextChoices):
|
||||||
LOW = 'L'
|
LOW = 'Low'
|
||||||
MEDIUM = 'M'
|
MEDIUM = 'Medium'
|
||||||
HIGH = 'H'
|
HIGH = 'High'
|
||||||
|
|
||||||
|
|
||||||
class Status(models.TextChoices):
|
class Status(models.TextChoices):
|
||||||
TODO = 'ToDo'
|
TODO = 'ToDo'
|
||||||
INPROGRESS = 'InProgress'
|
INPROGRESS = 'In Progress'
|
||||||
FINISHED = 'Finished'
|
FINISHED = 'Finished'
|
||||||
|
|
||||||
|
|
||||||
@ -65,13 +65,11 @@ class Issue(models.Model):
|
|||||||
title = models.CharField(max_length=255, verbose_name='title')
|
title = models.CharField(max_length=255, verbose_name='title')
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
status = models.CharField(Status.choices, max_length=15)
|
status = models.CharField(choices=Status.choices, max_length=15)
|
||||||
priority = models.CharField(Priority.choices, max_length=15)
|
priority = models.CharField(choices=Priority.choices, max_length=15)
|
||||||
tag = models.CharField(Tag.choices, max_length=15)
|
tag = models.CharField(choices=Tag.choices, max_length=15)
|
||||||
project = models.ForeignKey(Project,
|
project = models.ForeignKey(Project,
|
||||||
null=True,
|
on_delete=models.CASCADE)
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True)
|
|
||||||
author = models.ForeignKey(settings.AUTH_USER_MODEL,
|
author = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.DO_NOTHING,
|
||||||
related_name='issue_author', null=True)
|
related_name='issue_author', null=True)
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
from rest_framework.permissions import BasePermission
|
from rest_framework.permissions import BasePermission
|
||||||
|
from support.models import Project
|
||||||
|
|
||||||
class IsAuthor(BasePermission):
|
class IsAuthor(BasePermission):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, project):
|
def has_object_permission(self, request, view, object):
|
||||||
return bool(request.user
|
return bool(request.user
|
||||||
and request.user.is_authenticated
|
and request.user.is_authenticated
|
||||||
and request.user==project.author)
|
and request.user == object.author
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IsContributor(BasePermission):
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, object):
|
||||||
|
return bool(request.user
|
||||||
|
and request.user.is_authenticated
|
||||||
|
and request.user in object.contributors.all()
|
||||||
|
)
|
@ -1,6 +1,8 @@
|
|||||||
from rest_framework.serializers import (ModelSerializer,
|
from rest_framework.serializers import (ModelSerializer,
|
||||||
StringRelatedField,
|
StringRelatedField,
|
||||||
SlugRelatedField)
|
SlugRelatedField,
|
||||||
|
SerializerMethodField,
|
||||||
|
ValidationError)
|
||||||
from support.models import Project, ProjectContributor, Issue, Comment
|
from support.models import Project, ProjectContributor, Issue, Comment
|
||||||
|
|
||||||
|
|
||||||
@ -11,26 +13,85 @@ class ContributorSerializer(ModelSerializer):
|
|||||||
fields = ['contributor', 'project', 'data']
|
fields = ['contributor', 'project', 'data']
|
||||||
|
|
||||||
|
|
||||||
|
class ContributorListSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProjectContributor
|
||||||
|
fields = ['contributor']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSerializer(ModelSerializer):
|
class ProjectSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
author = StringRelatedField(many=False)
|
||||||
|
contributors = SlugRelatedField(many=True,
|
||||||
|
read_only='True',
|
||||||
|
slug_field='username')
|
||||||
|
class Meta:
|
||||||
|
model = Project
|
||||||
|
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,
|
contributors = SlugRelatedField(many=True,
|
||||||
read_only='True',
|
read_only='True',
|
||||||
slug_field='username')
|
slug_field='username')
|
||||||
author = StringRelatedField(many=False)
|
author = StringRelatedField(many=False)
|
||||||
|
issues = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
fields = ['id', 'title', 'date_created', 'type', 'description', 'author',
|
fields = ['title',
|
||||||
'contributors']
|
'date_created', 'type',
|
||||||
|
'author', 'contributors', 'description', 'issues']
|
||||||
|
|
||||||
|
def get_issues(self, instance):
|
||||||
|
queryset = Issue.objects.filter(project=instance.pk)
|
||||||
|
serializer = IssueSerializer(queryset, many=True)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
class ProjectDetailSerializer(ModelSerializer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class IssueSerializer(ModelSerializer):
|
class IssueSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
author = StringRelatedField(many=False)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
fields = ['title', 'date_created', 'priority', 'tag', 'status', 'author']
|
fields = ['id', 'title', 'project', 'date_created', 'priority',
|
||||||
|
'tag', 'status', 'author']
|
||||||
|
read_only_field = ['author']
|
||||||
|
|
||||||
|
|
||||||
|
def validate_author(self, data):
|
||||||
|
if Project.objects.filter(contributors=data.author).exists():
|
||||||
|
raise ValidationError("Requestor isn't contributor")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def validate_project(self, data):
|
||||||
|
# if data['user'] not in data['project'].contributors:
|
||||||
|
# raise ValidationError("User must be a contributor to the project")
|
||||||
|
#print(data.project)
|
||||||
|
#if self.context['request'].user not in data.contributors:
|
||||||
|
# raise ValidationError("User must be a contributor to the project")
|
||||||
|
#print(self.get_contributors(data))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class CommentListSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Comment
|
||||||
|
fields = ['title', 'date_created', 'author']
|
||||||
|
|
||||||
|
class CommentDetailSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Comment
|
||||||
|
fields = ['title', 'date_created', 'author', 'description']
|
@ -3,21 +3,33 @@ from rest_framework.serializers import raise_errors_on_nested_writes
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
from support.models import Project, ProjectContributor, Issue, Comment
|
from support.models import Project, ProjectContributor, Issue, Comment
|
||||||
from authentication.models import User
|
from authentication.models import User
|
||||||
from support.serializers import ProjectSerializer, ContributorSerializer
|
from support.serializers import (ProjectSerializer,
|
||||||
|
ProjectDetailSerializer,
|
||||||
|
ContributorSerializer,
|
||||||
|
IssueSerializer,
|
||||||
|
CommentListSerializer,
|
||||||
|
CommentDetailSerializer)
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import (IsAuthenticated,
|
from rest_framework.permissions import (IsAuthenticated,
|
||||||
IsAuthenticatedOrReadOnly)
|
IsAuthenticatedOrReadOnly)
|
||||||
from support.permissions import IsAuthor
|
from support.permissions import IsAuthor, IsContributor
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
|
||||||
|
|
||||||
class ProjectViewSet(ModelViewSet):
|
class ProjectViewSet(ModelViewSet):
|
||||||
permission_classes=[IsAuthenticatedOrReadOnly]
|
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||||
|
|
||||||
serializer_class = ProjectSerializer
|
serializer_class = ProjectSerializer
|
||||||
|
detail_serializer_class = ProjectDetailSerializer
|
||||||
|
|
||||||
queryset = Project.objects.filter(active=True)
|
queryset = Project.objects.filter(active=True)
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == 'retrieve':
|
||||||
|
return self.detail_serializer_class
|
||||||
|
return super().get_serializer_class()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
"""set authenticated user as author and contributor on creation"""
|
"""set authenticated user as author and contributor on creation"""
|
||||||
test = serializer.save(author=self.request.user)
|
test = serializer.save(author=self.request.user)
|
||||||
@ -27,7 +39,7 @@ class ProjectViewSet(ModelViewSet):
|
|||||||
contributor_serializer.save()
|
contributor_serializer.save()
|
||||||
|
|
||||||
@action(detail=True, methods=['patch'],
|
@action(detail=True, methods=['patch'],
|
||||||
permission_classes=[IsAuthor],
|
permission_classes=[IsContributor],
|
||||||
basename='add_contributor')
|
basename='add_contributor')
|
||||||
def add_contributor(self, request, pk):
|
def add_contributor(self, request, pk):
|
||||||
"""Create the user/project contributor's relation"""
|
"""Create the user/project contributor's relation"""
|
||||||
@ -47,4 +59,60 @@ class ProjectViewSet(ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class IssueViewSet(ModelViewSet):
|
class IssueViewSet(ModelViewSet):
|
||||||
serializer =
|
permission_classes = [IsContributor]
|
||||||
|
|
||||||
|
serializer_class = IssueSerializer
|
||||||
|
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
project_id = int(self.request.GET.get('project'))
|
||||||
|
project = Project.objects.get(id=project_id)
|
||||||
|
self.check_object_permissions(self.request, project)
|
||||||
|
return Issue.objects.filter(project=project_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_contributors(self, project):
|
||||||
|
queryset = ProjectContributor.objects.filter(project=project)
|
||||||
|
contributors_serializer = ContributorSerializer(queryset, many=True)
|
||||||
|
return contributors_serializer.data
|
||||||
|
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
print(request.data['project'])
|
||||||
|
project = Project.objects.get(id=request.data['project'])
|
||||||
|
serializer = IssueSerializer(data=request.data)
|
||||||
|
|
||||||
|
|
||||||
|
print(request.data['project'], type(request.data['project']))
|
||||||
|
print(self.get_contributors(request.data['project']))
|
||||||
|
|
||||||
|
if self.request.user in project.contributors:
|
||||||
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
serializer.author = self.request.user
|
||||||
|
serializer.save()
|
||||||
|
response = {
|
||||||
|
"message": f"Issue created for project {project}",
|
||||||
|
"data": serializer.data
|
||||||
|
}
|
||||||
|
return Response(response, status = status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
#def perform_create(self, serializer):
|
||||||
|
# """set authenticated user as author and contributor on creation"""
|
||||||
|
# serializer.save(author=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
class ContributorViewSet(ModelViewSet):
|
||||||
|
serializer_class = ContributorSerializer
|
||||||
|
queryset = ProjectContributor.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CommentViewSet(ModelViewSet):
|
||||||
|
serializer_class = CommentListSerializer
|
||||||
|
detail_serializer_class = CommentDetailSerializer
|
||||||
|
|
||||||
|
queryset = Comment.objects.all()
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == 'retrieve':
|
||||||
|
return self.detail_serializer_class
|
||||||
|
return super().get_serializer_class()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user