u
This commit is contained in:
parent
19907b1e83
commit
2adf87af1d
11 changed files with 272 additions and 239 deletions
|
|
@ -4,8 +4,8 @@ from .models import *
|
||||||
|
|
||||||
@admin.register(Course)
|
@admin.register(Course)
|
||||||
class CourseAdmin(admin.ModelAdmin):
|
class CourseAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'instructor', 'created_at', 'updated_at')
|
list_display = ('title', 'owner', 'created_at', 'updated_at')
|
||||||
search_fields = ('title', 'instructor__username')
|
search_fields = ('title', 'owner__username')
|
||||||
list_filter = ('created_at', 'updated_at')
|
list_filter = ('created_at', 'updated_at')
|
||||||
|
|
||||||
@admin.register(Module)
|
@admin.register(Module)
|
||||||
|
|
|
||||||
18
lms/app/migrations/0005_rename_instructor_course_owner.py
Normal file
18
lms/app/migrations/0005_rename_instructor_course_owner.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.10 on 2025-01-14 08:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0004_course_rating'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='course',
|
||||||
|
old_name='instructor',
|
||||||
|
new_name='owner',
|
||||||
|
),
|
||||||
|
]
|
||||||
21
lms/app/migrations/0006_module_created_by.py
Normal file
21
lms/app/migrations/0006_module_created_by.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 5.0.10 on 2025-01-14 11:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0005_rename_instructor_course_owner'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='module',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
lms/app/migrations/0007_alter_module_created_by.py
Normal file
21
lms/app/migrations/0007_alter_module_created_by.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 5.0.10 on 2025-01-14 11:10
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0006_module_created_by'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='module',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
lms/app/migrations/0008_lesson_created_by.py
Normal file
21
lms/app/migrations/0008_lesson_created_by.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 5.0.10 on 2025-01-14 12:12
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0007_alter_module_created_by'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='lesson',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.0.10 on 2025-01-14 13:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0008_lesson_created_by'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='lesson',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(null=True, verbose_name='Lesson Description'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='module',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(null=True, verbose_name='Module Description'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -13,7 +13,7 @@ class Course(models.Model):
|
||||||
image = models.ImageField(upload_to="courses/image", null=True)
|
image = models.ImageField(upload_to="courses/image", null=True)
|
||||||
is_paid = models.BooleanField(default=False)
|
is_paid = models.BooleanField(default=False)
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
||||||
instructor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='courses_taught', verbose_name="Instructor")
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='courses_taught', verbose_name="Instructor")
|
||||||
rating = models.PositiveSmallIntegerField(null=True, blank=True)
|
rating = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At")
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated At")
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated At")
|
||||||
|
|
@ -33,9 +33,10 @@ class Course(models.Model):
|
||||||
class Module(models.Model):
|
class Module(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
title = models.CharField(max_length=255, verbose_name="Module Title")
|
title = models.CharField(max_length=255, verbose_name="Module Title")
|
||||||
description = models.TextField(verbose_name="Module Description")
|
description = models.TextField(null=True, verbose_name="Module Description")
|
||||||
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='modules', verbose_name="Course")
|
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='modules', verbose_name="Course")
|
||||||
order = models.PositiveIntegerField(default=0, verbose_name="Order", unique=True)
|
order = models.PositiveIntegerField(default=0, verbose_name="Order", unique=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, verbose_name="Created By")
|
||||||
|
|
||||||
def str(self):
|
def str(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
@ -44,10 +45,12 @@ class Module(models.Model):
|
||||||
class Lesson(models.Model):
|
class Lesson(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
title = models.CharField(max_length=255, verbose_name="Lesson Title")
|
title = models.CharField(max_length=255, verbose_name="Lesson Title")
|
||||||
|
description = models.TextField(null=True, verbose_name="Lesson Description")
|
||||||
content = models.TextField(verbose_name="Lesson Content")
|
content = models.TextField(verbose_name="Lesson Content")
|
||||||
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='lessons', verbose_name="Module")
|
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='lessons', verbose_name="Module")
|
||||||
order = models.PositiveIntegerField(default=0, verbose_name="Order")
|
order = models.PositiveIntegerField(default=0, verbose_name="Order")
|
||||||
file = models.FileField(upload_to='lesson_files/', null=True, blank=True, verbose_name="Attached File")
|
file = models.FileField(upload_to='lesson_files/', null=True, blank=True, verbose_name="Attached File")
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, verbose_name="Created By")
|
||||||
|
|
||||||
def str(self):
|
def str(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,33 @@
|
||||||
from rest_framework.permissions import BasePermission
|
from rest_framework.permissions import IsAuthenticated, BasePermission, SAFE_METHODS
|
||||||
|
import logging
|
||||||
|
|
||||||
class IsInstructor(BasePermission):
|
logger = logging.getLogger(__name__)
|
||||||
"""
|
|
||||||
Custom permission to allow access only to users with role 'instructor'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
class IsOwnerOrReadOnly(BasePermission):
|
||||||
# Ensure the user is authenticated and has a role of 'instructor'
|
|
||||||
return request.user.is_authenticated and request.user.role == 'instructor'
|
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
|
||||||
|
if request.method in SAFE_METHODS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
view_name = view.__class__.__name__
|
||||||
|
match view_name:
|
||||||
|
case "CourseViewSet":
|
||||||
|
return obj.owner == request.user
|
||||||
|
|
||||||
|
case "ModuleViewSet":
|
||||||
|
return obj.created_by == request.user
|
||||||
|
|
||||||
|
case "LessonViewSet":
|
||||||
|
return obj.created_by == request.user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IsAdmin(BasePermission):
|
class IsAdmin(BasePermission):
|
||||||
"""
|
"""
|
||||||
Custom permission to allow access only to users with role 'instructor'.
|
Custom permission to allow access only to users with role 'instructor'.
|
||||||
|
|
|
||||||
|
|
@ -11,36 +11,58 @@ from dj_rest_auth.registration.serializers import RegisterSerializer
|
||||||
|
|
||||||
|
|
||||||
class CourseSerializer(serializers.ModelSerializer):
|
class CourseSerializer(serializers.ModelSerializer):
|
||||||
instructor_name = serializers.CharField(source='instructor.username', read_only=True)
|
owner_name = serializers.CharField(source='owner.username', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Course
|
model = Course
|
||||||
fields = ['id', 'title', 'description', 'is_paid', 'price', 'image', 'instructor_name', 'created_at', 'updated_at']
|
fields = ['id', 'title', 'description', 'is_paid', 'price', 'image', 'owner_name', 'created_at', 'updated_at']
|
||||||
read_only_fields = ['created_at', 'updated_at']
|
read_only_fields = ['created_at', 'updated_at']
|
||||||
|
|
||||||
class ModuleSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Module
|
|
||||||
fields = ['url', 'id', 'title', 'description', 'course', 'order']
|
|
||||||
extra_kwargs = {
|
|
||||||
'url': {'view_name': 'modules-detail', 'lookup_field': 'id'}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LessonSerializer(serializers.HyperlinkedModelSerializer):
|
class LessonSerializer(serializers.ModelSerializer):
|
||||||
module = serializers.PrimaryKeyRelatedField(queryset=Module.objects.all())
|
module = serializers.PrimaryKeyRelatedField(queryset=Module.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Lesson
|
model = Lesson
|
||||||
fields = ['url', 'id', 'title', 'content', 'module', 'order', 'file']
|
fields = ['id', 'title', 'description', 'content', 'module', 'order', 'file']
|
||||||
extra_kwargs = {
|
|
||||||
'url': {'view_name': 'lessons-detail', 'lookup_field': 'id'}
|
|
||||||
}
|
class ModuleSerializer(serializers.ModelSerializer):
|
||||||
|
lessons = serializers.SerializerMethodField()
|
||||||
|
class Meta:
|
||||||
|
model = Module
|
||||||
|
fields = ['id', 'title', 'description', 'lessons', 'course', 'order']
|
||||||
|
read_only_fields = ['course', 'lessons']
|
||||||
|
|
||||||
|
def get_lessons(self, obj):
|
||||||
|
return obj.lessons.values('id', 'title', 'description')
|
||||||
|
|
||||||
|
|
||||||
class EnrollmentSerializer(serializers.ModelSerializer):
|
class EnrollmentSerializer(serializers.ModelSerializer):
|
||||||
|
course_details = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Enrollment
|
model = Enrollment
|
||||||
fields = ['id', 'student', 'course', 'enrolled_at', 'completed']
|
fields = ['id', 'course', 'course_details', 'enrolled_at', 'completed']
|
||||||
|
read_only_fields = ['enrolled_at']
|
||||||
|
|
||||||
|
def get_course_details(self, obj):
|
||||||
|
course = obj.course
|
||||||
|
request = self.context.get('request')
|
||||||
|
image_url = course.image.url if course.image else None
|
||||||
|
|
||||||
|
if image_url and request:
|
||||||
|
image_url = request.build_absolute_uri(image_url)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": course.id,
|
||||||
|
"title": course.title,
|
||||||
|
"description": course.description,
|
||||||
|
"image": image_url,
|
||||||
|
"is_paid": course.is_paid,
|
||||||
|
"price": course.price,
|
||||||
|
"rating": course.rating,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QuizSerializer(serializers.ModelSerializer):
|
class QuizSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from .views import *
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'courses-read', CourseRead, basename='course-read')
|
|
||||||
router.register(r'courses', CourseViewSet, basename='course')
|
router.register(r'courses', CourseViewSet, basename='course')
|
||||||
router.register(r'modules', ModuleViewSet, basename='modules')
|
router.register(r'modules', ModuleViewSet, basename='modules')
|
||||||
router.register(r'lessons', LessonViewSet, basename='lessons')
|
router.register(r'lessons', LessonViewSet, basename='lessons')
|
||||||
|
|
|
||||||
308
lms/app/views.py
308
lms/app/views.py
|
|
@ -5,69 +5,37 @@ from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||||
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, BasePermission
|
from rest_framework.permissions import IsAuthenticated, BasePermission
|
||||||
from .permissions import IsInstructor, IsAdmin
|
from .permissions import IsOwnerOrReadOnly, IsAdmin
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
|
|
||||||
class CourseRead(ReadOnlyModelViewSet):
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
serializer_class = CourseSerializer
|
|
||||||
queryset = Course.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CourseViewSet(ModelViewSet):
|
class CourseViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
A ViewSet for viewing and editing Course instances.
|
A ViewSet for viewing and editing Course instances.
|
||||||
"""
|
"""
|
||||||
permission_classes = [IsAuthenticated,]
|
queryset = Course.objects.all()
|
||||||
|
permission_classes = [IsOwnerOrReadOnly]
|
||||||
serializer_class = CourseSerializer
|
serializer_class = CourseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""
|
|
||||||
Return courses belonging to the authenticated user.
|
|
||||||
"""
|
|
||||||
user = self.request.user
|
|
||||||
return Course.objects.filter(instructor=user)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
"""
|
"""
|
||||||
Save the post data when creating a new course.
|
Save the post data when creating a new course.
|
||||||
"""
|
"""
|
||||||
user = self.request.user
|
serializer.save(owner=self.request.user)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'], url_path='my-courses', url_name='my_courses')
|
||||||
serializer.save(instructor=user)
|
def get_my_course(self, request):
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
"""
|
"""
|
||||||
Ensure that only the instructor can update their course.
|
Custom GET method to fetch detailed information about my courses.
|
||||||
"""
|
"""
|
||||||
course = self.get_object()
|
|
||||||
if course.instructor != self.request.user:
|
my_courses = Course.objects.filter(owner=request.user)
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to edit this course."},
|
# Serialize the data
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
serializer = self.get_serializer(my_courses, many=True)
|
||||||
)
|
|
||||||
serializer.save()
|
return Response(serializer.data)
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
"""
|
|
||||||
Ensure that only the instructor can delete their course.
|
|
||||||
"""
|
|
||||||
if instance.instructor != self.request.user:
|
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to delete this course."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
instance.delete()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,15 +45,13 @@ class ModuleViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = ModuleSerializer
|
serializer_class = ModuleSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
||||||
lookup_field = 'id'
|
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Return modules only if the user is the course instructor.
|
Return modules only if the user is the course owner.
|
||||||
"""
|
"""
|
||||||
course_id = self.request.query_params.get('course_id')
|
course_id = self.request.query_params.get('pk')
|
||||||
if course_id:
|
if course_id:
|
||||||
course = Course.objects.filter(id=course_id).first()
|
course = Course.objects.filter(id=course_id).first()
|
||||||
if course:
|
if course:
|
||||||
|
|
@ -95,39 +61,18 @@ class ModuleViewSet(ModelViewSet):
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
"""
|
"""
|
||||||
Allow only the course instructor to create a module.
|
Allow only the course owner to create a module.
|
||||||
"""
|
"""
|
||||||
course_id = self.request.data.get('course')
|
course_id = self.request.data.get('course')
|
||||||
course = Course.objects.filter(id=course_id, instructor=self.request.user).first()
|
course = Course.objects.filter(id=course_id, owner=self.request.user).first()
|
||||||
if not course:
|
if not course:
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": "You do not have permission to delete this course."},
|
{"detail": "This course not found."},
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
serializer.save(course=course)
|
serializer.save(course=course, created_by=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
"""
|
|
||||||
Allow only the course instructor to update the module.
|
|
||||||
"""
|
|
||||||
module = self.get_object()
|
|
||||||
if module.course.instructor != self.request.user:
|
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to delete this course."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
serializer.save()
|
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
"""
|
|
||||||
Allow only the course instructor to delete the module.
|
|
||||||
"""
|
|
||||||
if instance.course.instructor != self.request.user:
|
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to delete this course."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
instance.delete()
|
|
||||||
|
|
||||||
class LessonViewSet(ModelViewSet):
|
class LessonViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -135,143 +80,83 @@ class LessonViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = LessonSerializer
|
serializer_class = LessonSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
||||||
lookup_field = 'id'
|
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Return lessons only if the user is authorized (instructor or student) and provides valid data.
|
Return a specific lesson within a specific module only if the user is authorized.
|
||||||
"""
|
"""
|
||||||
# حالة جلب كائن واحد
|
lesson_id = self.request.query_params.get('lesson_id') # Get lesson ID from the request
|
||||||
if self.kwargs.get(self.lookup_field): # 'id' by default
|
module_id = self.request.query_params.get('module_id') # Get module ID from the request
|
||||||
lesson = Lesson.objects.filter(id=self.kwargs[self.lookup_field]).first()
|
|
||||||
if lesson:
|
|
||||||
course = lesson.module.course
|
|
||||||
# التحقق من الصلاحيات
|
|
||||||
if course.instructor == self.request.user or Enrollment.objects.filter(student=self.request.user, course=course).exists():
|
|
||||||
return Lesson.objects.filter(id=lesson.id)
|
|
||||||
return Lesson.objects.none()
|
|
||||||
|
|
||||||
# حالة جلب مجموعة بناءً على module_id
|
# Check if both lesson_id and module_id are provided
|
||||||
module_id = self.request.query_params.get('module_id')
|
if not lesson_id or not module_id:
|
||||||
if module_id:
|
return Lesson.objects.none() # Return no results if either is missing
|
||||||
module = Module.objects.filter(id=module_id).first()
|
|
||||||
if module:
|
|
||||||
course = module.course
|
|
||||||
# التحقق من الصلاحيات
|
|
||||||
if course.instructor == self.request.user or Enrollment.objects.filter(student=self.request.user, course=course).exists():
|
|
||||||
queryset = Lesson.objects.filter(module=module).order_by('order')
|
|
||||||
print(f"Queryset: {queryset}")
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# في حالة عدم وجود صلاحيات أو عدم تطابق البيانات
|
# Verify that the module exist
|
||||||
return Lesson.objects.none()
|
module = Module.objects.filter(id=module_id).first()
|
||||||
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
"""
|
|
||||||
Allow only the course instructor to create a lesson within their module.
|
|
||||||
"""
|
|
||||||
module_id = self.request.data.get('module')
|
|
||||||
module = Module.objects.filter(id=module_id, course__instructor=self.request.user).first()
|
|
||||||
if not module:
|
if not module:
|
||||||
return Response(
|
return Lesson.objects.none() # Return no results if the module does not exist
|
||||||
{"detail": "You do not have permission to create a lesson in this module."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
# Verify that the lesson exists and is associated with the module
|
||||||
)
|
lesson = Lesson.objects.filter(id=lesson_id, module=module).first()
|
||||||
serializer.save(module=module)
|
if not lesson:
|
||||||
|
return Lesson.objects.none() # Return no results if the lesson does not exist or is not linked to the module
|
||||||
|
|
||||||
|
# Check if the user has access (owner of the course or enrolled in the course)
|
||||||
|
is_owner = module.course.owner == self.request.user
|
||||||
|
is_enrolled = Enrollment.objects.filter(course=module.course, student=self.request.user).exists()
|
||||||
|
|
||||||
|
if is_owner or is_enrolled:
|
||||||
|
return Lesson.objects.filter(id=lesson_id) # Return the lesson if the user is authorized
|
||||||
|
|
||||||
|
return Lesson.objects.none() # Deny access if the user is not authorized
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
"""
|
|
||||||
Allow only the course instructor to update a lesson within their module.
|
|
||||||
"""
|
|
||||||
lesson = self.get_object()
|
|
||||||
if lesson.module.course.instructor != self.request.user:
|
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to update this lesson."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
serializer.save()
|
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
"""
|
|
||||||
Allow only the course instructor to delete a lesson within their module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if instance.module.course.instructor != self.request.user:
|
|
||||||
return Response(
|
|
||||||
{"detail": "You do not have permission to delete this lesson."},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
instance.delete()
|
|
||||||
|
|
||||||
class EnrollmentViewSet(ModelViewSet):
|
class EnrollmentViewSet(ModelViewSet):
|
||||||
queryset = Enrollment.objects.all()
|
queryset = Enrollment.objects.all()
|
||||||
serializer_class = EnrollmentSerializer
|
serializer_class = EnrollmentSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
http_method_names = ['get', 'post', 'delete']
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
instance = Enrollment.objects.filter(user=request.user)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(instance)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
# Ensure the user is an instructor
|
|
||||||
if request.user.role != 'instructor':
|
|
||||||
return Response({"error": "Only instructors can enroll students"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Get student and course data from the request
|
|
||||||
student_id = request.data.get('student_id')
|
|
||||||
course_id = request.data.get('course_id')
|
course_id = request.data.get('course_id')
|
||||||
|
|
||||||
# Check if the student and course exist
|
# Check if the student and course exist
|
||||||
try:
|
try:
|
||||||
student = User.objects.get(id=student_id, role='student')
|
|
||||||
course = Course.objects.get(id=course_id)
|
course = Course.objects.get(id=course_id)
|
||||||
except User.DoesNotExist:
|
|
||||||
return Response({"error": "Student not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
except Course.DoesNotExist:
|
except Course.DoesNotExist:
|
||||||
return Response({"error": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"detail": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Ensure the current instructor is the course instructor
|
|
||||||
if course.instructor != request.user:
|
|
||||||
return Response({"error": "You can only enroll students in your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
|
if Enrollment.objects.filter(student=request.user, course=course).exists():
|
||||||
|
return Response({"detail": "You are already subscribed to this course."}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
elif course.owner == request.user:
|
||||||
|
return Response({"detail": "You can't enroll in your course"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Create a new enrollment
|
# Create a new enrollment
|
||||||
enrollment = Enrollment.objects.create(student=student, course=course)
|
enrollment = Enrollment.objects.create(student=request.user, course=course)
|
||||||
serializer = self.get_serializer(enrollment)
|
serializer = self.get_serializer(enrollment)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
|
||||||
# Ensure the user is an instructor
|
|
||||||
if request.user.role != 'instructor':
|
|
||||||
return Response({"error": "Only instructors can update enrollments"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Get the enrollment object to update
|
|
||||||
enrollment = self.get_object()
|
|
||||||
|
|
||||||
# Ensure the current instructor is the course instructor
|
|
||||||
if enrollment.course.instructor != request.user:
|
|
||||||
return Response({"error": "You can only update enrollments in your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Update the enrollment
|
|
||||||
serializer = self.get_serializer(enrollment, data=request.data, partial=True)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
serializer.save()
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
# Ensure the user is an instructor
|
|
||||||
if request.user.role != 'instructor':
|
|
||||||
return Response({"error": "Only instructors can delete enrollments"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Get the enrollment object to delete
|
# Get the enrollment object to delete
|
||||||
enrollment = self.get_object()
|
enrollment = self.get_object()
|
||||||
|
if enrollment.student != request.user:
|
||||||
# Ensure the current instructor is the course instructor
|
raise PermissionDenied("You do not have permission to delete this enrollment.")
|
||||||
if enrollment.course.instructor != request.user:
|
|
||||||
return Response({"error": "You can only delete enrollments in your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Delete the enrollment
|
# Delete the enrollment
|
||||||
enrollment.delete()
|
enrollment.delete()
|
||||||
return Response({"message": "Enrollment deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
|
return Response({"detail": "Enrollment deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class QuizViewSet(ModelViewSet):
|
class QuizViewSet(ModelViewSet):
|
||||||
queryset = Quiz.objects.all()
|
queryset = Quiz.objects.all()
|
||||||
|
|
@ -279,19 +164,19 @@ class QuizViewSet(ModelViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
|
||||||
# Ensure the user is an instructor
|
# Ensure the user is an owner
|
||||||
if request.user.role != 'instructor':
|
if request.user.role != 'owner':
|
||||||
return Response({"error": "Only instructors can create quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "Only owners can create quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Get course data from the request
|
# Get course data from the request
|
||||||
moduleId = request.data.get('module')
|
moduleId = request.data.get('module')
|
||||||
# Check if the course exists
|
# Check if the course exists
|
||||||
try:
|
try:
|
||||||
module = Module.objects.get(id=moduleId)
|
module = Module.objects.get(id=moduleId)
|
||||||
except Course.DoesNotExist:
|
except Course.DoesNotExist:
|
||||||
return Response({"error": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"detail": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
# Ensure the current instructor is the course instructor
|
# Ensure the current owner is the course owner
|
||||||
if module.course.instructor != request.user:
|
if module.course.owner != request.user:
|
||||||
return Response({"error": "You can only create quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "You can only create quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Create a new quiz
|
# Create a new quiz
|
||||||
# data = request.data.copy() # نسخ البيانات لتجنب التعديل على الأصل
|
# data = request.data.copy() # نسخ البيانات لتجنب التعديل على الأصل
|
||||||
# data.pop('module', None) # إزالة المفتاح module إذا كان موجودًا
|
# data.pop('module', None) # إزالة المفتاح module إذا كان موجودًا
|
||||||
|
|
@ -301,14 +186,14 @@ class QuizViewSet(ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
# Ensure the user is an instructor
|
# Ensure the user is an owner
|
||||||
if request.user.role != 'instructor':
|
if request.user.role != 'owner':
|
||||||
return Response({"error": "Only instructors can update quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "Only owners can update quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Get the quiz object to update
|
# Get the quiz object to update
|
||||||
quiz = self.get_object()
|
quiz = self.get_object()
|
||||||
# Ensure the current instructor is the course instructor
|
# Ensure the current owner is the course owner
|
||||||
if quiz.module.course.instructor != request.user:
|
if quiz.module.course.owner != request.user:
|
||||||
return Response({"error": "You can only update quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "You can only update quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Update the quiz
|
# Update the quiz
|
||||||
serializer = self.get_serializer(quiz, data=request.data, partial=True)
|
serializer = self.get_serializer(quiz, data=request.data, partial=True)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
@ -316,17 +201,17 @@ class QuizViewSet(ModelViewSet):
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
|
||||||
# Ensure the user is an instructor
|
# Ensure the user is an owner
|
||||||
if request.user.role != 'instructor':
|
if request.user.role != 'owner':
|
||||||
return Response({"error": "Only instructors can delete quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "Only owners can delete quizzes"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Get the quiz object to delete
|
# Get the quiz object to delete
|
||||||
quiz = self.get_object()
|
quiz = self.get_object()
|
||||||
# Ensure the current instructor is the course instructor
|
# Ensure the current owner is the course owner
|
||||||
if quiz.module.course.instructor != request.user:
|
if quiz.module.course.owner != request.user:
|
||||||
return Response({"error": "You can only delete quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "You can only delete quizzes for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# Delete the quiz
|
# Delete the quiz
|
||||||
quiz.delete()
|
quiz.delete()
|
||||||
return Response({"message": "Quiz deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
|
return Response({"detail": "Quiz deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class CertificateViewSet(ModelViewSet):
|
class CertificateViewSet(ModelViewSet):
|
||||||
|
|
@ -336,7 +221,8 @@ class CertificateViewSet(ModelViewSet):
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
if self.action == 'create':
|
if self.action == 'create':
|
||||||
permission_classes = [IsInstructor]
|
# permission_classes = [Isowner]
|
||||||
|
pass
|
||||||
elif self.action in ['update', 'destroy']:
|
elif self.action in ['update', 'destroy']:
|
||||||
permission_classes = [IsAdmin]
|
permission_classes = [IsAdmin]
|
||||||
else:
|
else:
|
||||||
|
|
@ -354,12 +240,12 @@ class CertificateViewSet(ModelViewSet):
|
||||||
course = Course.objects.get(id=courseId)
|
course = Course.objects.get(id=courseId)
|
||||||
student = User.objects.get(id=student_id, role='student')
|
student = User.objects.get(id=student_id, role='student')
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return Response({"error": "Student not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"detail": "Student not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
except Course.DoesNotExist:
|
except Course.DoesNotExist:
|
||||||
return Response({"error": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"detail": "Course not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
# Ensure the current instructor is the course instructor
|
# Ensure the current owner is the course owner
|
||||||
if course.instructor != request.user:
|
if course.owner != request.user:
|
||||||
return Response({"error": "You can only create certificate for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": "You can only create certificate for your own courses"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
certificate = Certificate.objects.create(course=course, student=student)
|
certificate = Certificate.objects.create(course=course, student=student)
|
||||||
serializer = self.get_serializer(certificate)
|
serializer = self.get_serializer(certificate)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue