Initial commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 07:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ExamSet',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Section',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('section_type', models.CharField(choices=[('listening', 'Listening'), ('reading', 'Reading'), ('writing', 'Writing'), ('speaking', 'Speaking')], max_length=20)),
|
||||
('time_limit_minutes', models.PositiveIntegerField()),
|
||||
('audio_file', models.FileField(blank=True, null=True, upload_to='audio/')),
|
||||
('exam_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='exams.examset')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveIntegerField()),
|
||||
('question_type', models.CharField(choices=[('mcq', 'Multiple Choice'), ('gap', 'Gap Fill'), ('matching', 'Matching'), ('essay', 'Essay'), ('speaking', 'Speaking Prompt')], max_length=20)),
|
||||
('prompt', models.TextField()),
|
||||
('options', models.JSONField(blank=True, null=True)),
|
||||
('correct_answer', models.TextField(blank=True, null=True)),
|
||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='exams.section')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StudentAttempt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('started_at', models.DateTimeField(auto_now_add=True)),
|
||||
('submitted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('is_complete', models.BooleanField(default=False)),
|
||||
('exam_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exams.examset')),
|
||||
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StudentAnswer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('answer_text', models.TextField(blank=True)),
|
||||
('is_correct', models.BooleanField(null=True)),
|
||||
('manual_score', models.FloatField(blank=True, null=True)),
|
||||
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exams.question')),
|
||||
('attempt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='exams.studentattempt')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-13 06:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='section',
|
||||
name='passage_text',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 11:24
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0002_section_passage_text'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddConstraint(
|
||||
model_name='studentanswer',
|
||||
constraint=models.UniqueConstraint(fields=('attempt', 'question'), name='unique_answer_per_attempt_question'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='studentattempt',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('is_complete', False)), fields=('student', 'exam_set'), name='unique_active_attempt_per_exam'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 11:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0003_studentanswer_unique_answer_per_attempt_question_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='question',
|
||||
options={'ordering': ['order', 'id']},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='section',
|
||||
options={'ordering': ['order', 'id']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='section',
|
||||
name='order',
|
||||
field=models.PositiveIntegerField(default=1),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='question',
|
||||
constraint=models.UniqueConstraint(fields=('section', 'order'), name='unique_question_order_per_section'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='section',
|
||||
constraint=models.UniqueConstraint(fields=('exam_set', 'order'), name='unique_section_order_per_exam'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 11:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0004_alter_question_options_alter_section_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='studentattempt',
|
||||
name='current_section',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='exams.section'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='studentattempt',
|
||||
name='section_deadline',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='studentattempt',
|
||||
name='section_started_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 12:46
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0005_studentattempt_current_section_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='examset',
|
||||
name='description',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='examset',
|
||||
name='is_published',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='studentanswer',
|
||||
name='manual_score',
|
||||
field=models.FloatField(blank=True, help_text='Manual band score from 0.0 to 9.0 for writing or speaking responses.', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(9)]),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("exams", "0008_examset_category")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="question",
|
||||
name="explanation",
|
||||
field=models.TextField(blank=True, help_text="Shown to students in review mode after they submit the test."),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="question",
|
||||
name="passage_reference",
|
||||
field=models.TextField(blank=True, help_text="Optional exact sentence or short excerpt from the reading passage that supports this answer."),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 13:00
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0006_examset_description_examset_is_published_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='studentanswer',
|
||||
name='audio_response',
|
||||
field=models.FileField(blank=True, help_text='Optional speaking response. Maximum size: 20 MB.', null=True, upload_to='speaking_responses/%Y/%m/', validators=[django.core.validators.FileExtensionValidator(['mp3', 'm4a', 'wav', 'webm', 'ogg'])]),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-17 13:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0007_studentanswer_audio_response'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='examset',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('reading', 'Reading'), ('listening', 'Listening'), ('writing', 'Writing'), ('speaking', 'Speaking'), ('full', 'Full Mock Test')], default='full', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("exams", "0007_question_explanation_question_passage_reference")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="examset",
|
||||
name="delivery_mode",
|
||||
field=models.CharField(
|
||||
choices=[("native", "Adaptive platform test"), ("exact_html", "Exact uploaded HTML")],
|
||||
default="native",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="examset",
|
||||
name="source_html",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-19 16:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exams', '0009_examset_exact_html'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuestionGroup',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.SlugField(help_text='Short identifier used by Excel, for example notes_1.', max_length=60)),
|
||||
('order', models.PositiveIntegerField(default=1)),
|
||||
('layout_type', models.CharField(choices=[('notes', 'Notes / summary'), ('table', 'Table / form'), ('flow', 'Flow chart')], default='notes', max_length=20)),
|
||||
('title', models.CharField(blank=True, max_length=200)),
|
||||
('instructions', models.TextField(blank=True)),
|
||||
('layout_html', models.TextField(help_text='Formatted worksheet HTML. Insert blanks with [[question number]], for example [[1]].')),
|
||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='question_groups', to='exams.section')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='questions', to='exams.questiongroup'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='questiongroup',
|
||||
constraint=models.UniqueConstraint(fields=('section', 'key'), name='unique_question_group_key_per_section'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("exams", "0010_questiongroup_question_group_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="examset",
|
||||
name="access_level",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("free", "Free"),
|
||||
("premium", "Lifetime Premium"),
|
||||
],
|
||||
db_index=True,
|
||||
default="free",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user