Skip to content

Commit 24f08bc

Browse files
authored
Merge pull request #7 from MikaTech-dev/feat/serializers
Feat/serializers
2 parents 0417dd8 + 6b3c508 commit 24f08bc

5 files changed

Lines changed: 124 additions & 10 deletions

File tree

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ disable=missing-module-docstring,
77
missing-class-docstring,
88
missing-function-docstring,
99
too-few-public-methods,
10-
invalid-name
10+
invalid-name,
11+
imported-auth-user
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 6.0 on 2026-01-02 20:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='question',
15+
name='correct_answers',
16+
field=models.JSONField(default=dict, help_text="e.g. {'answer': 'B'}"),
17+
),
18+
]

api/models.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from django.db import models
2-
from django.contrib.auth.models import User
3-
from django.utils import timezone
2+
3+
# Create your models here.
4+
from django.contrib.auth.models import User # Inbuilt auth User model
45

56
class Exam(models.Model):
67
title = models.CharField(max_length=255)
7-
duration = models.DurationField()
8+
duration = models.DurationField()
89
course_name = models.CharField(max_length=255)
910
metadata = models.TextField(blank=True)
1011
created_at = models.DateTimeField(auto_now_add=True)
@@ -13,18 +14,23 @@ def __str__(self):
1314
return self.title
1415

1516
class Question(models.Model):
17+
# Defining enums/choices
1618
QUESTION_TYPES = [
1719
('MCQ', 'Multiple Choice'),
1820
('SA', 'Short Answer'),
1921
]
2022

21-
exam = models.ForeignKey(Exam, related_name='questions', on_delete=models.CASCADE)
23+
exam: Exam = models.ForeignKey(Exam, related_name='questions', on_delete=models.CASCADE)
2224
question_text = models.TextField()
23-
question_type = models.CharField(max_length=3, choices=QUESTION_TYPES, default='MCQ')
25+
question_type = models.CharField(
26+
max_length=3, choices=QUESTION_TYPES,
27+
default='MCQ'
28+
) # using choices
2429

25-
options = models.JSONField(default=dict, blank=True, help_text="For MCQs: {'options': ['A', 'B', 'C']}")
26-
correct_answers = models.JSONField(help_text="e.g. {'answer': 'B'}")
27-
30+
# Flexible storage for options (e.g. ["A", "B", "C"]) and correct answers
31+
options = models.JSONField(
32+
default=dict, blank=True, help_text="For MCQs: {'options': ['A', 'B', 'C']}")
33+
correct_answers = models.JSONField(default=dict, help_text="e.g. {'answer': 'B'}")
2834
order = models.PositiveSmallIntegerField(default=1)
2935

3036
class Meta:
@@ -39,10 +45,12 @@ class Submission(models.Model):
3945
('graded', 'Graded'),
4046
]
4147

48+
# Link the submission to the inbuilt User model via FK
4249
student = models.ForeignKey(User, on_delete=models.CASCADE)
4350
exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
4451
submitted_at = models.DateTimeField(auto_now_add=True)
4552

53+
# Allow score and feedback to be blank (nullable) initially until graded
4654
total_score = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
4755
feedback = models.TextField(blank=True)
4856
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending')
@@ -55,6 +63,7 @@ class Answer(models.Model):
5563
question = models.ForeignKey(Question, on_delete=models.CASCADE)
5664
student_answer = models.JSONField()
5765

66+
# Optional: store individual score per question if needed later
5867
is_correct = models.BooleanField(default=False, blank=True)
5968

6069
def __str__(self):

api/serializers.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from rest_framework import serializers
2+
from django.contrib.auth.models import User
3+
from .models import Exam, Question, Submission, Answer
4+
5+
class UserSerializer(serializers.ModelSerializer):
6+
class Meta:
7+
model = User
8+
fields = ['id', 'username', 'email']
9+
10+
class QuestionSerializer(serializers.ModelSerializer):
11+
class Meta:
12+
model = Question
13+
fields = ['id', 'question_text', 'question_type', 'options', 'order']
14+
15+
class ExamSerializer(serializers.ModelSerializer):
16+
# Embedding qustions inside the exam details
17+
questions = QuestionSerializer(many=True, read_only=True)
18+
19+
class Meta:
20+
model = Exam
21+
fields = ['id', 'title', 'duration', 'course_name', 'metadata', 'questions', 'created_at']
22+
23+
class AnswerSerializer(serializers.ModelSerializer):
24+
class Meta:
25+
model = Answer
26+
fields = ['question', 'student_answer', 'is_correct']
27+
read_only_fields = ['is_correct']
28+
29+
class SubmissionSerializer(serializers.ModelSerializer):
30+
answers = AnswerSerializer(many=True)
31+
student = UserSerializer(read_only=True)
32+
33+
class Meta:
34+
model = Submission
35+
fields = [
36+
'id',
37+
'student',
38+
'exam',
39+
'submitted_at',
40+
'total_score',
41+
'feedback',
42+
'status',
43+
'answers'
44+
]
45+
# These fields will br created/filled by the grading.service(TBD), not by the student
46+
read_only_fields = ['total_score', 'feedback', 'status', 'submitted_at']
47+
def create(self, validated_data):
48+
"""
49+
Handles creating the Submission AND the nested Answers in one go.
50+
"""
51+
answers_data = validated_data.pop('answers')
52+
# Creating the Submission instance
53+
submission = Submission.objects.create(**validated_data)
54+
55+
# Creating the Answers instance
56+
for answer_data in answers_data:
57+
Answer.objects.create(submission=submission, **answer_data)
58+
59+
return submission

api/views.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1-
from django.shortcuts import render
1+
# from django.shortcuts import render
22

33
# Create your views here.
4+
from rest_framework import viewsets, permissions
5+
from .models import Exam, Submission
6+
from .serializers import ExamSerializer, SubmissionSerializer
7+
8+
class ExamViewSet(viewsets.ReadOnlyModelViewSet):
9+
"""
10+
API endpoint that allows exams to be viewed or listed.
11+
READ-ONLY: Users (Students) cannot create or modify exams here.
12+
"""
13+
queryset = Exam.objects.all()
14+
serializer_class = ExamSerializer
15+
permission_classes = [permissions.IsAuthenticated]
16+
17+
class SubmissionViewSet(viewsets.ModelViewSet):
18+
"""
19+
API endpoint for students to submit exams and view their submission history.
20+
"""
21+
serializer_class = SubmissionSerializer
22+
permission_classes = [permissions.IsAuthenticated]
23+
24+
def get_queryset(self):
25+
# Ensuring users only have access to their own submission
26+
return Submission.objects.filter(student=self.request.user)
27+
28+
def perform_create(self, serializer):
29+
# link submission to the actual user
30+
serializer.save(student=self.request.user)

0 commit comments

Comments
 (0)