Skip to content

Commit 706bd4f

Browse files
Merge branch 'main' into adding-to-ci-pipeline
2 parents 04aa734 + 0049d40 commit 706bd4f

40 files changed

+1065
-53
lines changed

backend/apps/lessons/admin.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
from django.contrib import admin
2+
from .models import Assignment, Quiz, Question, Choice, Solution
23

3-
# Register your models here.
4+
admin.site.register(Assignment)
5+
admin.site.register(Quiz)
6+
admin.site.register(Question)
7+
admin.site.register(Choice)
8+
admin.site.register(Solution)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Generated by Django 5.1.6 on 2025-03-20 07:23
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='Assignment',
17+
fields=[
18+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('title', models.CharField(max_length=200)),
20+
('description', models.TextField()),
21+
('assignment_type', models.CharField(choices=[('EX', 'Exercises'), ('HW', 'Homework'), ('QZ', 'Quiz'), ('TT', 'Tests')], max_length=2)),
22+
('deadline', models.DateTimeField()),
23+
],
24+
),
25+
migrations.CreateModel(
26+
name='Question',
27+
fields=[
28+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29+
('question_type', models.CharField(choices=[('MC', 'Multiple Choice'), ('SA', 'Short Answer')], max_length=2)),
30+
('order_of_question', models.PositiveIntegerField()),
31+
('question_text', models.TextField()),
32+
],
33+
),
34+
migrations.CreateModel(
35+
name='Choice',
36+
fields=[
37+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38+
('choice_text', models.CharField(max_length=255)),
39+
('is_correct', models.BooleanField(default=False)),
40+
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='lessons.question')),
41+
],
42+
),
43+
migrations.CreateModel(
44+
name='Quiz',
45+
fields=[
46+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47+
('time_limit', models.PositiveIntegerField(help_text='Time limit for the quiz (in minutes)')),
48+
('num_of_questions', models.PositiveIntegerField()),
49+
('attempts', models.PositiveIntegerField(default=1)),
50+
('is_active', models.BooleanField(default=True)),
51+
('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lessons.assignment')),
52+
],
53+
),
54+
migrations.AddField(
55+
model_name='question',
56+
name='quiz',
57+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lessons.quiz'),
58+
),
59+
migrations.CreateModel(
60+
name='Solution',
61+
fields=[
62+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63+
('short_answer_text', models.TextField(blank=True, null=True)),
64+
('choices', models.ManyToManyField(blank=True, to='lessons.choice')),
65+
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lessons.question')),
66+
],
67+
),
68+
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.1.6 on 2025-03-20 21:26
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('lessons', '0001_initial'),
12+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='assignment',
18+
name='student',
19+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='student', to=settings.AUTH_USER_MODEL),
20+
),
21+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.6 on 2025-03-21 08:02
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('lessons', '0002_assignment_student'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='question',
15+
name='points',
16+
field=models.PositiveIntegerField(default=1, help_text='Points assigned to this question'),
17+
),
18+
]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Generated by Django 5.1.6 on 2025-03-22 05:33
2+
3+
import django.core.validators
4+
import django.db.models.deletion
5+
from django.conf import settings
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('lessons', '0003_question_points'),
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.AlterModelOptions(
18+
name='quiz',
19+
options={'verbose_name': 'Quiz', 'verbose_name_plural': 'Quizzes'},
20+
),
21+
migrations.AlterField(
22+
model_name='assignment',
23+
name='assignment_type',
24+
field=models.CharField(choices=[('EX', 'Exercises'), ('HW', 'Homework'), ('QZ', 'Quiz'), ('TT', 'Tests'), ('EC', 'Extra Credit')], max_length=2),
25+
),
26+
migrations.AlterField(
27+
model_name='assignment',
28+
name='deadline',
29+
field=models.DateTimeField(blank=True, null=True),
30+
),
31+
migrations.AlterField(
32+
model_name='assignment',
33+
name='description',
34+
field=models.TextField(blank=True),
35+
),
36+
migrations.AlterField(
37+
model_name='assignment',
38+
name='student',
39+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to=settings.AUTH_USER_MODEL),
40+
),
41+
migrations.AlterField(
42+
model_name='quiz',
43+
name='attempts',
44+
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]),
45+
),
46+
migrations.AlterField(
47+
model_name='quiz',
48+
name='time_limit',
49+
field=models.IntegerField(blank=True, help_text='Time limit for the quiz (in minutes)', null=True),
50+
),
51+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.6 on 2025-03-22 05:43
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('lessons', '0004_alter_quiz_options_alter_assignment_assignment_type_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='quiz',
15+
name='time_limit',
16+
field=models.PositiveIntegerField(blank=True, help_text='Time limit for the quiz (in minutes)', null=True),
17+
),
18+
]

backend/apps/lessons/models.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,96 @@
11
from django.db import models
2+
from django.contrib.auth.models import User
3+
# from backend.apps.uploads.models import UploadRecord - This will be revised and corrected
4+
from django.core.exceptions import ValidationError
5+
from django.core.validators import MinValueValidator
26

3-
# Create your models here.
7+
8+
class Assignment(models.Model): # Assignments: Represents assignments given to individual students.
9+
ASSIGNMENT_TYPES = [
10+
('EX', 'Exercises'),
11+
('HW', 'Homework'),
12+
('QZ', 'Quiz'),
13+
('TT', 'Tests'),
14+
# Can add more if needed...
15+
('EC', 'Extra Credit')
16+
]
17+
# Fields
18+
title = models.CharField(max_length=200)
19+
description = models.TextField(blank=True)
20+
assignment_type = models.CharField(max_length=2, choices=ASSIGNMENT_TYPES)
21+
22+
# Upload_record (ForeignKey to UploadRecord) - Currently not referenced correctly but will be when branch is updated
23+
# upload_record = models.ForeignKey(UploadRecord, on_delete=models.SET_NULL, null=True, blank=True)
24+
25+
# Student (ForeignKey to User as there's no model exclusively for student)
26+
student = models.ForeignKey(User, on_delete=models.CASCADE, related_name="assignments", null=True, blank=True)
27+
# ^ Currently student is temporarily allowed to be nullable. Can be manually updated in a later time.
28+
29+
deadline = models.DateTimeField(null=True, blank=True) # deadline is optional for extra credit/optional assignments
30+
31+
def __str__(self):
32+
return self.title
33+
34+
35+
class Quiz(models.Model): # Quizzes: Represents quizzes linked to assignments.
36+
class Meta:
37+
verbose_name = 'Quiz'
38+
verbose_name_plural = 'Quizzes'
39+
# Fields
40+
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
41+
time_limit = models.PositiveIntegerField(null=True, blank=True, help_text="Time limit for the quiz (in minutes)")
42+
num_of_questions = models.PositiveIntegerField()
43+
attempts = models.IntegerField(validators=[MinValueValidator(1)]) # Ensures that at least 1 attempt is required
44+
is_active = models.BooleanField(default=True)
45+
46+
def __str__(self):
47+
return f"Quiz for {self.assignment.title}"
48+
49+
50+
class Question(models.Model): # Questions: Represents individual questions within a quiz.
51+
QUESTION_TYPES = [
52+
('MC', 'Multiple Choice'),
53+
('SA', 'Short Answer'),
54+
]
55+
# Fields
56+
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
57+
question_type = models.CharField(max_length=2, choices=QUESTION_TYPES)
58+
order_of_question = models.PositiveIntegerField()
59+
question_text = models.TextField()
60+
# New field to store points/score for the question
61+
points = models.PositiveIntegerField(default=1, help_text="Points assigned to this question")
62+
63+
def __str__(self):
64+
quiz_title = self.quiz.assignment.title if self.quiz and self.quiz.assignment else "Unknown Quiz"
65+
return f"Question {self.order_of_question} - {quiz_title}"
66+
67+
68+
class Choice(models.Model): # Choices: Store the possible answer choices for multiple choice questions.
69+
# Fields
70+
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices")
71+
choice_text = models.CharField(max_length=255)
72+
is_correct = models.BooleanField(default=False)
73+
74+
def __str__(self):
75+
return f"Choice: {self.choice_text} ({'Correct' if self.is_correct else 'Incorrect'})"
76+
77+
78+
class Solution(models.Model): # Solutions: Represents correct answers to questions.
79+
# Fields
80+
question = models.ForeignKey(Question, on_delete=models.CASCADE)
81+
choices = models.ManyToManyField(Choice, blank=True) # for multiple choice questions
82+
short_answer_text = models.TextField(blank=True, null=True) # for short answer questions
83+
84+
# New validation that ensures MC questions have at least one correct answer
85+
def clean(self):
86+
if self.question.question_type == 'MC' and not self.choices.filter(is_correct=True).exists():
87+
raise ValidationError("A multiple-choice question must have at least one correct answer.")
88+
89+
def __str__(self):
90+
if self.question.question_type == 'MC':
91+
correct_choices = ", ".join(choice.choice_text for choice in self.choices.all())
92+
return f"Solution for Question {self.question.id}: {correct_choices}"
93+
elif self.question.question_type == 'SA':
94+
return f"Solution for Question {self.question.id}: {self.short_answer_text[:30]}"
95+
else:
96+
return f"Solution for Question {self.question.id}: (No Answer Set)"

backend/apps/notifications/migrations/0003_alter_notification_info_category_and_more.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
21
# Generated by Django 5.1.6 on 2025-03-13 20:40
32

3+
44
from django.db import migrations, models
55

66

backend/apps/pomodoro/admin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
11
from django.contrib import admin
22

33
# Register your models here.
4+
from .models import PomodoroSession, PetCatalog, PetCollection
5+
6+
# ai-gen start (ChatGPT-3.5, 0)
7+
@admin.register(PomodoroSession)
8+
class PomodoroSessionAdmin(admin.ModelAdmin):
9+
list_display = ('student', 'duration', 'is_completed', 'pet_earned', 'start_time', 'end_time')
10+
list_filter = ('is_completed', 'pet_earned', 'start_time')
11+
search_fields = ('student__username', 'task_description')
12+
ordering = ('-start_time',)
13+
14+
@admin.register(PetCatalog)
15+
class PetCatalogAdmin(admin.ModelAdmin):
16+
list_display = ('name', 'pet_type', 'rarity', 'drop_rate', 'image_preview')
17+
list_filter = ('rarity',)
18+
search_fields = ('name', 'pet_type', 'description')
19+
20+
def image_preview(self, obj):
21+
if obj.image:
22+
return f"{obj.image.url}"
23+
return "No Image"
24+
image_preview.short_description = "Image Preview"
25+
26+
@admin.register(PetCollection)
27+
class PetCollectionAdmin(admin.ModelAdmin):
28+
list_display = ('student', 'pet_catalog', 'acquired_at', 'is_active')
29+
list_filter = ('is_active', 'acquired_at')
30+
search_fields = ('student__username', 'pet_catalog__name')
31+
ordering = ('-acquired_at',)
32+
# ai-gen end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Generated by Django 5.1.7 on 2025-03-21 07:38
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='PetCatalog',
19+
fields=[
20+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21+
('name', models.CharField(max_length=100)),
22+
('pet_type', models.CharField(max_length=100)),
23+
('image', models.ImageField(blank=True, null=True, upload_to=None)),
24+
('description', models.TextField()),
25+
('rarity', models.CharField(choices=[('common', 'Common'), ('rare', 'Rare'), ('epic', 'Epic'), ('legendary', 'Legendary')], max_length=50)),
26+
('drop_rate', models.FloatField()),
27+
],
28+
options={
29+
'verbose_name': 'Pet Catalog',
30+
'verbose_name_plural': 'Pet Catalogs',
31+
},
32+
),
33+
migrations.CreateModel(
34+
name='PetCollection',
35+
fields=[
36+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
37+
('acquired_at', models.DateTimeField(auto_now_add=True)),
38+
('is_active', models.BooleanField(default=False)),
39+
('pet_catalog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pomodoro.petcatalog')),
40+
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
41+
],
42+
options={
43+
'verbose_name': 'Pet Collection',
44+
'verbose_name_plural': 'Pet Collections',
45+
},
46+
),
47+
migrations.CreateModel(
48+
name='PomodoroSession',
49+
fields=[
50+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
51+
('duration', models.IntegerField(default=25)),
52+
('is_completed', models.BooleanField(default=False)),
53+
('task_description', models.TextField(blank=True, null=True)),
54+
('pet_earned', models.BooleanField(default=False)),
55+
('start_time', models.DateTimeField(auto_now_add=True)),
56+
('end_time', models.DateTimeField(blank=True, null=True)),
57+
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
58+
],
59+
options={
60+
'verbose_name': 'Pomodoro Session',
61+
'verbose_name_plural': 'Pomodoro Sessions',
62+
},
63+
),
64+
]

0 commit comments

Comments
 (0)