working on permission is_contributor; checkpoint

This commit is contained in:
yann 2025-05-30 10:24:01 +02:00
parent 776ba21695
commit 3636d4a72b
12 changed files with 268 additions and 32 deletions

View File

@ -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'],

View File

@ -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),
} }

View File

@ -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),

View File

@ -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)

View File

@ -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),
),
]

View File

@ -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),
),
]

View 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'),
),
]

View 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'),
),
]

View File

@ -45,9 +45,9 @@ 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):
@ -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)

View File

@ -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()
)

View File

@ -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']

View File

@ -3,12 +3,17 @@ 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
@ -16,8 +21,15 @@ 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()