Files
training-software/courses/migrations/0001_initial.py
Paperclip CTO cfa9ad6f53 feat(TRA-236,TRA-238): M2 course domain model and dwell-time tracking
TRA-236 — Course domain model:
- courses/models.py: Course → Module → Lesson → Page hierarchy with UUID
  PKs, ordering fields, required_seconds navigation gate, version tracking,
  and UniqueConstraint per (parent, order) pair
- courses/migrations/0001_initial.py: initial migration (applies cleanly on
  a fresh DB)
- tests/test_course_domain.py: migration smoke, relation integrity, cascade
  delete, ordering, and uniqueness-constraint tests

TRA-238 — Dwell-time tracking:
- tracking/models.py: Enrollment, PageProgress (can_advance property), and
  DwellEvent models appended alongside existing AuditEvent
- tracking/services.py: record_dwell_event, compute_eligible_seconds (pure),
  check_navigation_gate, _recompute_accumulated — reconnect merging within
  RECONNECT_TOLERANCE_SECONDS and anti-idle cap at MAX_VALID_EVENT_SECONDS
- tracking/migrations/0001_initial.py: updated to include all four models
  (AuditEvent, Enrollment, PageProgress, DwellEvent) with FK dependencies
  on courses.Course and courses.Page
- tests/test_dwell_tracking.py: event replay, reconnect tolerance, anti-idle
  cap, gate pass/block, and can_advance DB integration tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00

164 lines
5.7 KiB
Python

import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Course",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("org_id", models.UUIDField(db_index=True)),
("title", models.CharField(max_length=255)),
("slug", models.SlugField(max_length=255, unique=True)),
("description", models.TextField(blank=True)),
("version", models.PositiveIntegerField(default=1)),
("is_published", models.BooleanField(default=False)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["title"],
},
),
migrations.CreateModel(
name="Module",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"course",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="modules",
to="courses.course",
),
),
("title", models.CharField(max_length=255)),
("order", models.PositiveIntegerField(default=0)),
("version", models.PositiveIntegerField(default=1)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["order"],
},
),
migrations.AddConstraint(
model_name="module",
constraint=models.UniqueConstraint(
fields=("course", "order"), name="unique_course_module_order"
),
),
migrations.CreateModel(
name="Lesson",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"module",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lessons",
to="courses.module",
),
),
("title", models.CharField(max_length=255)),
("order", models.PositiveIntegerField(default=0)),
("version", models.PositiveIntegerField(default=1)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["order"],
},
),
migrations.AddConstraint(
model_name="lesson",
constraint=models.UniqueConstraint(
fields=("module", "order"), name="unique_module_lesson_order"
),
),
migrations.CreateModel(
name="Page",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"lesson",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="pages",
to="courses.lesson",
),
),
("title", models.CharField(max_length=255)),
("order", models.PositiveIntegerField(default=0)),
(
"content_type",
models.CharField(
choices=[
("text", "Text"),
("video", "Video"),
("quiz", "Quiz"),
("embed", "Embed"),
],
default="text",
max_length=20,
),
),
("content_body", models.TextField(blank=True)),
("is_required", models.BooleanField(default=True)),
("required_seconds", models.PositiveIntegerField(default=0)),
("version", models.PositiveIntegerField(default=1)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["order"],
},
),
migrations.AddConstraint(
model_name="page",
constraint=models.UniqueConstraint(
fields=("lesson", "order"), name="unique_lesson_page_order"
),
),
]