7 changed files with 205 additions and 35 deletions
-
18apps/course/access.py
-
37apps/course/serializers/lesson.py
-
133apps/course/tests/test_course_access_guards.py
-
7apps/course/views/lesson.py
-
5apps/quiz/serializers/participant.py
-
21apps/quiz/serializers/quiz.py
-
17apps/quiz/views/quiz.py
@ -0,0 +1,18 @@ |
|||
from apps.course.models import Participant |
|||
|
|||
|
|||
def user_has_course_access(user, course): |
|||
if not user or not getattr(user, 'is_authenticated', False): |
|||
return False |
|||
|
|||
if user.is_staff or user.is_superuser: |
|||
return True |
|||
|
|||
if course.professor_id == user.id: |
|||
return True |
|||
|
|||
return Participant.objects.filter( |
|||
student_id=user.id, |
|||
course=course, |
|||
is_active=True, |
|||
).exists() |
|||
@ -0,0 +1,133 @@ |
|||
from django.core.files.uploadedfile import SimpleUploadedFile |
|||
from rest_framework import status |
|||
from rest_framework.test import APITestCase |
|||
|
|||
from apps.account.models import ProfessorUser, StudentUser |
|||
from apps.course.models.course import Course, CourseCategory |
|||
from apps.course.models.lesson import CourseChapter, CourseLesson, Lesson |
|||
from apps.quiz.models.quiz import Quiz, Question |
|||
|
|||
|
|||
class CourseAccessGuardsTests(APITestCase): |
|||
def setUp(self): |
|||
self.professor = ProfessorUser.objects.create( |
|||
email='prof-guard@example.com', |
|||
fullname='Professor Guard', |
|||
experience_years=5, |
|||
) |
|||
self.outsider = StudentUser.objects.create( |
|||
email='outsider@example.com', |
|||
fullname='Outsider Student', |
|||
) |
|||
self.category = CourseCategory.objects.create( |
|||
name='Guard Category', |
|||
slug='guard-category', |
|||
) |
|||
thumbnail = SimpleUploadedFile('guard.jpg', b'filecontent', content_type='image/jpeg') |
|||
self.course = Course.objects.create( |
|||
title='Guard Course', |
|||
slug='guard-course', |
|||
category=self.category, |
|||
professor=self.professor, |
|||
thumbnail=thumbnail, |
|||
video_type=Course.VedioTypeChoices.YOUTUBE_LINK, |
|||
video_link='https://example.com/video', |
|||
is_online=False, |
|||
level=Course.LevelChoices.BEGINNER, |
|||
duration=10, |
|||
lessons_count=0, |
|||
description='Description', |
|||
short_description='Short description', |
|||
status=Course.StatusChoices.ONGOING, |
|||
is_free=True, |
|||
) |
|||
self.chapter = CourseChapter.objects.create( |
|||
course=self.course, |
|||
title='Chapter 1', |
|||
priority=1, |
|||
is_active=True, |
|||
) |
|||
lesson = Lesson.objects.create( |
|||
title='Lesson 1', |
|||
content_type=Lesson.ContentTypeChoices.VIDEO_FILE, |
|||
duration=5, |
|||
) |
|||
self.course_lesson = CourseLesson.objects.create( |
|||
course=self.course, |
|||
chapter=self.chapter, |
|||
lesson=lesson, |
|||
priority=1, |
|||
is_active=True, |
|||
) |
|||
self.quiz = Quiz.objects.create( |
|||
lesson=self.course_lesson, |
|||
course=self.course, |
|||
title='Guard Quiz', |
|||
description='Quiz description', |
|||
each_question_timing=30, |
|||
status=True, |
|||
) |
|||
self.question = Question.objects.create( |
|||
quiz=self.quiz, |
|||
question='What is 2+2?', |
|||
option1='4', |
|||
option2='3', |
|||
option3='2', |
|||
option4='1', |
|||
correct_answer=1, |
|||
priority=1, |
|||
) |
|||
|
|||
def test_lesson_list_marks_lessons_and_quizzes_inactive_without_course_access(self): |
|||
self.client.force_authenticate(user=self.outsider) |
|||
|
|||
response = self.client.get(f'/api/courses/{self.course.slug}/lessons/') |
|||
|
|||
self.assertEqual(response.status_code, status.HTTP_200_OK) |
|||
lesson_data = response.data['results'][0] |
|||
self.assertFalse(lesson_data['permission']) |
|||
self.assertFalse(lesson_data['is_active']) |
|||
self.assertFalse(lesson_data['quizs'][0]['permission']) |
|||
|
|||
def test_lesson_list_v2_marks_chapter_inactive_without_course_access(self): |
|||
self.client.force_authenticate(user=self.outsider) |
|||
|
|||
response = self.client.get(f'/api/courses/v2/{self.course.slug}/lessons/') |
|||
|
|||
self.assertEqual(response.status_code, status.HTTP_200_OK) |
|||
chapter_data = response.data['results'][0] |
|||
self.assertFalse(chapter_data['is_active']) |
|||
self.assertFalse(chapter_data['lessons'][0]['is_active']) |
|||
|
|||
def test_quiz_detail_rejects_users_without_course_access(self): |
|||
self.client.force_authenticate(user=self.outsider) |
|||
|
|||
response = self.client.get(f'/api/quiz/{self.quiz.id}/') |
|||
|
|||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
|||
|
|||
def test_quiz_submit_rejects_users_without_course_access(self): |
|||
self.client.force_authenticate(user=self.outsider) |
|||
|
|||
payload = { |
|||
'quiz': self.quiz.id, |
|||
'started_at': '2026-06-02T10:00:00Z', |
|||
'ended_at': '2026-06-02T10:01:00Z', |
|||
'total_timing': 60, |
|||
'question_score': 1, |
|||
'timing_score': 1, |
|||
'total_score': 2, |
|||
'answers': [ |
|||
{ |
|||
'question': self.question.id, |
|||
'option_num': 1, |
|||
'at_time': '2026-06-02T10:00:30Z', |
|||
'answer_timing': 30, |
|||
} |
|||
], |
|||
} |
|||
|
|||
response = self.client.post('/api/quiz/submit-quiz/', payload, format='json') |
|||
|
|||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
|||
self.assertIn('quiz', response.data) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue