Commit 04bcc8c0 authored by Martin Juhás's avatar Martin Juhás
Browse files

Merge branch...

Merge branch '537-add-endpoint-for-retrieving-last-unanswered-questionnaire-for-a-trainee' into 'main'

Resolve "Add endpoint for retrieving last unanswered questionnaire for a trainee"

See merge request inject/backend!472
parents d7cff085 b6eb2bf0
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -299,6 +299,26 @@ paths:
          $ref: "#/components/responses/Success"
        '500':
          $ref: "#/components/responses/Error"
  /progress/:
    get:
      tags:
        - trainee-progress
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                username:
                  type: string
              required:
                - username
      responses:
        '200':
          $ref: "#/components/responses/TraineeProgressResponse"
        '500':
          $ref: "#/components/responses/Error"

  /auth/login/:
    post:
@@ -446,6 +466,45 @@ components:
              description: Session ID for the authenticated user
          required:
            - sessionid
    TraineeProgressResponse:
      type: object
      properties:
        opensearch_index:
          type: string
        questionnaire:
          type: object
          properties:
            name:
              type: string
              description: Display name of the questionnaire
            content:
              type: string
              description: Description of the questionnaire
            questions:
              type: array
              items:
                type: object
                properties:
                  content:
                    type: string
                    description: Question text
                  "type":
                    type: string
                    enum:
                      - Radio
                      - Free-form
                      - Auto-free-form
                      - Multiple-choice
                  labels:
                    type: array
                    items:
                      type: string
                required:
                  - content
                  - type
      required:
        - questionnaire
        - opensearch_index

  responses:
    Success:
@@ -466,3 +525,10 @@ components:
        application/json:
          schema:
            $ref: "#/components/schemas/AuthResponse"
    TraineeProgressResponse:
      description: "Response to the trainee progress endpoint"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/TraineeProgressResponse"
+103 −0
Original line number Diff line number Diff line
from typing import Optional, Any, Dict

from common_lib.logger import logger
from exercise.models import (
    ExerciseAccess,
    OpenSearchAccess,
    TeamQuestionnaireState,
)
from exercise_definition.models import Questionnaire, Question, QuestionTypes
from user.models import User


class TraineeProgress:
    opensearch_index: str
    questionnaire: Optional[Questionnaire]

    def __init__(self):
        self.opensearch_index = ""
        self.questionnaire = None


def get_trainee_progress(username: str) -> TraineeProgress:
    progress = TraineeProgress()
    access = (
        ExerciseAccess.objects.filter(
            user__username=username,
            user__is_active=True,
            user__is_imported=False,
            entered_at__isnull=False,
            group=User.AuthGroup.TRAINEE,
        )
        .order_by("-entered_at")
        .first()
    )

    if access is None:
        logger.info(f"could not find user {username}, cannot retrieve progress")
        return progress

    opensearch_access = OpenSearchAccess.objects.filter(
        team_id=access.team_ids[0]
    ).first()
    if opensearch_access is not None:
        progress.opensearch_index = opensearch_access.index_name

    tqs = (
        TeamQuestionnaireState.objects.select_related(
            "questionnaire", "questionnaire__content"
        )
        .filter(
            team_id=access.team_ids[0],
            status=TeamQuestionnaireState.Status.SENT,
        )
        .order_by("-timestamp_sent")
        .first()
    )

    if tqs is not None:
        progress.questionnaire = tqs.questionnaire
    else:
        logger.info(
            f"did not find an unanswered questionnaire for user {username}"
        )

    return progress


def serialize_question(question: Question) -> dict:
    out = dict()

    out["content"] = question.content.raw
    out["type"] = question.type.capitalize()

    if (
        question.type == QuestionTypes.RADIO
        or question.type == QuestionTypes.MULTIPLE_CHOICE
    ):
        out["labels"] = question.details.labels

    return out


def serialize_questionnaire(questionnaire: Optional[Questionnaire]) -> dict:
    out: Dict[str, Any] = dict()
    if questionnaire is None:
        return out

    print(questionnaire)

    out["name"] = questionnaire.display_name
    out["content"] = questionnaire.content.raw
    out["questions"] = [
        serialize_question(question)
        for question in questionnaire.questions.select_related("content").all()
    ]
    return out


def serialize_progress(progress: TraineeProgress) -> dict:
    return {
        "opensearch_index": progress.opensearch_index,
        "questionnaire": serialize_questionnaire(progress.questionnaire),
    }
+2 −3
Original line number Diff line number Diff line
from datetime import datetime
from typing import List, Optional

from graphene import DateTime

from exercise.models import Team, ExerciseState, ExerciseAccess
from running_exercise.models import (
    ActionLog,
@@ -30,7 +29,7 @@ def create_action_log(
    requires_attention: bool = False,
    previous_logs: List[ActionLog] = [],
    user: Optional[User] = None,
    timestamp: Optional[DateTime] = None,
    timestamp: Optional[datetime] = None,
) -> ActionLog:
    log = ActionLog(
        type=LogType.get_type(details),
+19 −0
Original line number Diff line number Diff line
# Generated by Django 3.2.25 on 2025-12-09 14:43

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        ('running_exercise', '0026_remove_sandboxlogdetails_sandbox_timestamp'),
    ]

    operations = [
        migrations.AlterField(
            model_name='actionlog',
            name='timestamp',
            field=models.DateTimeField(default=django.utils.timezone.now),
        ),
    ]
+5 −0
Original line number Diff line number Diff line
@@ -18,4 +18,9 @@ urlpatterns = [
        views.CreateSandboxLogView.as_view(),
        name="create-sandbox-log",
    ),
    path(
        "progress/",
        views.TraineeProgressView.as_view(),
        name="get-trainee-progress",
    ),
]
Loading