<filename>backend/app/model/questionnaires/chain_session.py
from marshmallow import fields, missing, pre_load
from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr

from app import db, ma
from app.export_service import ExportService
from app.schema.model_schema import ModelSchema
from app.model.questionnaires.chain_session_step import ChainSessionStepSchema, ChainSessionStep


class ChainSession(db.Model):
    """
    SkillStar: Chain Session
    """
    __tablename__ = "chain_session"
    __label__ = "SkillSTAR Chain Session"
    __no_export__ = True  # This will be transferred as a part of a parent class
    __question_type__ = ExportService.TYPE_UNRESTRICTED
    __estimated_duration_minutes__ = 5

    id = db.Column(db.Integer, primary_key=True)
    last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
    time_on_task_ms = db.Column(db.BigInteger, default=0)

    chain_questionnaire_id = db.Column(
        "chain_questionnaire_id",
        db.Integer,
        db.ForeignKey('chain_questionnaire.id')
    )

    date = db.Column(
            db.DateTime(timezone=True),
            info={
                "display_order": 1,
                "type": "datepicker",
                "template_options": {
                    "required": True,
                    "label": 'Session Date',
                },
            },
        )

    completed = db.Column(
            db.Boolean,
            info={
                "display_order": 2,
                "type": "radio",
                "template_options": {
                    "required": True,
                    "label": 'Session Complete?',
                    "options": [
                        {"value": True, "label": "Yes"},
                        {"value": False, "label": "No"},
                    ],
                },
            },
        )

    session_type = db.Column(
            db.String,
            info={
                "display_order": 2,
                "type": "radio",
                "template_options": {
                    "required": True,
                    "label": 'Session Type',
                    "options": [
                        {"value": 'training', "label": "Training"},
                        {"value": 'probe', "label": "Probe"},
                        {"value": 'booster', "label": "Booster"},
                    ],
                },
            },
        )

    step_attempts = db.relationship(
            "ChainSessionStep",
            backref='chain_session',
            cascade="all, delete-orphan",
            passive_deletes=True
        )

    def get_field_groups(self):
        field_groups = {
            "step_attempts": {
                "type": "repeat",
                "display_order": 3,
                "wrappers": ["card"],
                "repeat_class": ChainSessionStep,
                "template_options": {
                    "label": "Which tasks were attempted?",
                    "description": "Add a step",
                },
                "expression_properties": {},
            },
        }
        return field_groups


class ChainSessionSchema(ModelSchema):
    @pre_load
    def set_field_session(self, data, **kwargs):
        self.fields['step_attempts'].schema.session = self.session
        return data

    class Meta(ModelSchema.Meta):
        model = ChainSession
        fields = (
            "id",
            "last_updated",
            "participant_id",
            "user_id",
            "time_on_task_ms",
            "date",
            "completed",
            "session_type",
            "session_number",
            "chain_questionnaire_id",
            "step_attempts",
        )

    step_attempts = fields.Nested(ChainSessionStepSchema, many=True)
    participant_id = fields.Method('get_participant_id', dump_only=True)
    user_id = fields.Method('get_user_id', dump_only=True)
    session_number = fields.Method('get_session_number', dump_only=True)

    def get_participant_id(self, obj):
        if obj is None:
            return missing

        return obj.chain_questionnaire.participant_id

    def get_user_id(self, obj):
        if obj is None:
            return missing

        return obj.chain_questionnaire.user_id

    def get_session_number(self, obj):
        if obj is None:
            return missing

        # Sort sessions by date
        sorted_sessions = sorted(obj.chain_questionnaire.sessions, key=lambda k: k.date)

        # Find this session and return its index, incremented.
        for i, session in enumerate(sorted_sessions):
            if obj.id == session.id:
                return i + 1

        # Session not found.
        return -1

<filename>backend/app/model/questionnaires/chain_session.py
from marshmallow import fields, missing, pre_load
from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr

from app import db, ma
from app.export_service import ExportService
from app.schema.model_schema import ModelSchema
from app.model.questionnaires.chain_session_step import ChainSessionStepSchema, ChainSessionStep


class ChainSession(db.Model):
    """
    SkillStar: Chain Session
    """
    __tablename__ = "chain_session"
    __label__ = "SkillSTAR Chain Session"
    __no_export__ = True  # This will be transferred as a part of a parent class
    __question_type__ = ExportService.TYPE_UNRESTRICTED
    __estimated_duration_minutes__ = 5

    id = db.Column(db.Integer, primary_key=True)
    last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
    time_on_task_ms = db.Column(db.BigInteger, default=0)

    chain_questionnaire_id = db.Column(
        "chain_questionnaire_id",
        db.Integer,
        db.ForeignKey('chain_questionnaire.id')
    )

    date = db.Column(
            db.DateTime(timezone=True),
            info={
                "display_order": 1,
                "type": "datepicker",
                "template_options": {
                    "required": True,
                    "label": 'Session Date',
                },
            },
        )

    completed = db.Column(
            db.Boolean,
            info={
                "display_order": 2,
                "type": "radio",
                "template_options": {
                    "required": True,
                    "label": 'Session Complete?',
                    "options": [
                        {"value": True, "label": "Yes"},
                        {"value": False, "label": "No"},
                    ],
                },
            },
        )

    session_type = db.Column(
            db.String,
            info={
                "display_order": 2,
                "type": "radio",
                "template_options": {
                    "required": True,
                    "label": 'Session Type',
                    "options": [
                        {"value": 'training', "label": "Training"},
                        {"value": 'probe', "label": "Probe"},
                        {"value": 'booster', "label": "Booster"},
                    ],
                },
            },
        )

    step_attempts = db.relationship(
            "ChainSessionStep",
            backref='chain_session',
            cascade="all, delete-orphan",
            passive_deletes=True
        )

    def get_field_groups(self):
        field_groups = {
            "step_attempts": {
                "type": "repeat",
                "display_order": 3,
                "wrappers": ["card"],
                "repeat_class": ChainSessionStep,
                "template_options": {
                    "label": "Which tasks were attempted?",
                    "description": "Add a step",
                },
                "expression_properties": {},
            },
        }
        return field_groups


class ChainSessionSchema(ModelSchema):
    @pre_load
    def set_field_session(self, data, **kwargs):
        self.fields['step_attempts'].schema.session = self.session
        return data

    class Meta(ModelSchema.Meta):
        model = ChainSession
        fields = (
            "id",
            "last_updated",
            "participant_id",
            "user_id",
            "time_on_task_ms",
            "date",
            "completed",
            "session_type",
            "session_number",
            "chain_questionnaire_id",
            "step_attempts",
        )

    step_attempts = fields.Nested(ChainSessionStepSchema, many=True)
    participant_id = fields.Method('get_participant_id', dump_only=True)
    user_id = fields.Method('get_user_id', dump_only=True)
    session_number = fields.Method('get_session_number', dump_only=True)

    def get_participant_id(self, obj):
        if obj is None:
            return missing

        return obj.chain_questionnaire.participant_id

    def get_user_id(self, obj):
        if obj is None:
            return missing

        return obj.chain_questionnaire.user_id

    def get_session_number(self, obj):
        if obj is None:
            return missing

        # Sort sessions by date
        sorted_sessions = sorted(obj.chain_questionnaire.sessions, key=lambda k: k.date)

        # Find this session and return its index, incremented.
        for i, session in enumerate(sorted_sessions):
            if obj.id == session.id:
                return i + 1

        # Session not found.
        return -1

SkillStar: Chain Session # This will be transferred as a part of a parent class # Sort sessions by date # Find this session and return its index, incremented. # Session not found.