Trilha de cursos

TLDR: Criar o model Trail para agrupar cursos em sequência ordenada, com tabela intermediária, admin, backfill e endpoints GET.

Contexto

Hoje os cursos existem de forma independente. A feature de trilha agrupa cursos em uma sequência guiada — análoga à jornada de áudios — permitindo que um curso dependa do anterior dentro da trilha.

Objetivos

  • Criar o model Trail com os campos code, title, description, is_active, sort
  • Criar a tabela intermediária TrailCourse (trail + course + sort)
  • Backfill: criar a trilha padrão e associar os cursos existentes por ordem de sort
  • Admin: criar/editar trilhas com cursos inline; exibir a trilha no admin de Course
  • Endpoints GET: listar trilhas, detalhe de trilha, cursos de uma trilha
  • Testes para models, admin e views

Fora de escopo

— (não registrado na spec original)

Mudanças

Novos arquivos

  • apps/courses/models/trail.py — models Trail e TrailCourse
  • apps/courses/migrations/0023_trail_trailcourse.py — cria as tabelas
  • apps/courses/migrations/0024_backfill_default_trail.py — seed: trilha padrão + cursos existentes
  • apps/courses/views/trail_api.py — TrailListAPIView, TrailDetailAPIView, TrailCoursesAPIView
  • tests/courses/test_trail.py — testes de model, admin e views

Arquivos modificados

  • apps/courses/models/__init__.py — exportar Trail, TrailCourse
  • apps/courses/admin.py — TrailAdmin (com TrailCourseInline) + exibir a trilha no CourseAdmin
  • apps/courses/serializers.py — TrailSerializer, TrailCourseSerializer
  • apps/courses/urls.py — 3 novas rotas de trail
  • apps/courses/views/__init__.py — exportar as novas views

Model Trail

python code UniqueCodeField(size_code=6) title CharField(255) description RichTextField(blank, null) is_active BooleanField(default=True) sort PositiveIntegerField(default=0) # Meta: ordering = ['sort'], verbose_name = "Trilha"

Tabela intermediária TrailCourse

python trail ForeignKey(Trail, CASCADE, related_name="trail_courses") course ForeignKey(Course, CASCADE, related_name="trail_courses") sort PositiveIntegerField() # unique_together: (trail, course), (trail, sort) # ordering: ['sort']

Backfill (migration)

  • get_or_create de Trail com code="TRAIL001", title="Trilha Padrão"
  • Se criada: associar todos os Course ativos, ordenados por sort, via TrailCourse

Endpoints

| Método | URL | View | |—|—|—| | GET | /v1/courses/trails | TrailListAPIView | | GET | /v1/courses/trails/<trail_id> | TrailDetailAPIView | | GET | /v1/courses/trails/<trail_id>/courses | TrailCoursesAPIView |

Serializers

  • TrailSerializer: id, code, title, description, is_active, sort, qty_courses
  • TrailCourseSerializer: usa CourseSerializer para cada curso e adiciona o sort da trilha

Como verificar

  1. python manage.py migrate sem erros
  2. No admin: criar trilha, adicionar cursos via inline, reordenar via sort
  3. GET /v1/courses/trails → lista trilhas ativas
  4. GET /v1/courses/trails/1 → detalhe da trilha
  5. GET /v1/courses/trails/1/courses → cursos da trilha em ordem
  6. pytest tests/courses/test_trail.py → todos passam

Documentação

Documentar a regra de negócio de trilha e a dependência entre cursos.