Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Exam model with V3 question_sources, update relevant JS utils #11025

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion kolibri/core/assets/src/exams/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,32 @@ function annotateQuestionsWithItem(questions) {
});
}

/* Given a V2 question_sources, return V3 structure with those questions within one new section */
export function convertV2toV3(questionSources = []) {
return {
section_title: '', // TODO Get a default string for the section title in here that is translated
description: '',
resource_pool: [],
questions: annotateQuestionsWithItem(questionSources),
rtibbles marked this conversation as resolved.
Show resolved Hide resolved
};
}

export function revertV3toV2(questionSources) {
if (!questionSources.length) {
return [];
}
return questionSources[0].questions;
}

export function convertExamQuestionSourcesToV3(exam, extraArgs = {}) {
if (exam.data_model_version !== 3) {
const V2_sources = convertExamQuestionSources(exam, extraArgs);
return [convertV2toV3(V2_sources)];
}

return exam.question_sources;
}

export function convertExamQuestionSources(exam, extraArgs = {}) {
const { data_model_version } = exam;
if (data_model_version === 0) {
Expand All @@ -107,12 +133,22 @@ export function convertExamQuestionSources(exam, extraArgs = {}) {
if (data_model_version === 1) {
return annotateQuestionsWithItem(convertExamQuestionSourcesV1V2(exam.question_sources));
}
// For backwards compatibility. If you are using V3, use the convertExamQuestionSourcesToV3 func
if (data_model_version === 3) {
rtibbles marked this conversation as resolved.
Show resolved Hide resolved
return revertV3toV2(exam.question_sources);
}

return annotateQuestionsWithItem(exam.question_sources);
}

export function fetchNodeDataAndConvertExam(exam) {
const { data_model_version } = exam;
if (data_model_version >= 2) {
if (data_model_version >= 3) {
/* For backwards compatibility, we need to convert V3 to V2 */
exam.question_sources = revertV3toV2(exam.question_sources);
rtibbles marked this conversation as resolved.
Show resolved Hide resolved
return Promise.resolve(exam);
}
if (data_model_version == 2) {
exam.question_sources = annotateQuestionsWithItem(exam.question_sources);
return Promise.resolve(exam);
}
Expand Down
59 changes: 58 additions & 1 deletion kolibri/core/assets/test/exams/utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import map from 'lodash/map';
import { convertExamQuestionSources } from '../../src/exams/utils';
import { convertExamQuestionSources, convertExamQuestionSourcesToV3 } from '../../src/exams/utils';

// map of content IDs to lists of question IDs
const QUESTION_IDS = {
Expand Down Expand Up @@ -81,6 +81,63 @@ const contentNodes = map(QUESTION_IDS, (assessmentIds, nodeId) => {
});

describe('exam utils', () => {
describe('convertExamQuestionSourcesToV3 converting from any previous version to V3', () => {
it('returns an array of newly structured objects with old question sources in questions', () => {
// Stolen from test below to ensure we're getting expected V2 values... as expected
const exam = {
data_model_version: 1,
question_sources: [
{
exercise_id: 'E1',
question_id: 'Q1',
title: 'Question 1',
},
{
exercise_id: 'E1',
question_id: 'Q2',
title: 'Question 2',
},
{
exercise_id: 'E2',
question_id: 'Q1',
title: 'Question 1',
},
],
};
const expectedSources = [
{
exercise_id: 'E1',
question_id: 'Q1',
title: 'Question 1',
counter_in_exercise: 1,
item: 'E1:Q1',
},
{
exercise_id: 'E1',
question_id: 'Q2',
title: 'Question 2',
counter_in_exercise: 2,
item: 'E1:Q2',
},
{
exercise_id: 'E2',
question_id: 'Q1',
title: 'Question 1',
counter_in_exercise: 1,
item: 'E2:Q1',
},
].sort();
const converted = convertExamQuestionSourcesToV3(exam);
expect(converted).toEqual([
{
section_title: '',
description: '',
resource_pool: [],
questions: expectedSources.sort(),
},
]);
});
});
describe('convertExamQuestionSources converting from V1 to V2', () => {
it('returns a question_sources array with a counter_in_exercise field', () => {
const exam = {
Expand Down
21 changes: 21 additions & 0 deletions kolibri/core/exams/migrations/0007_bump_data_model_version_to_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2023-07-27 20:52
from __future__ import unicode_literals

from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("exams", "0006_nullable_creator_assigned_by"),
]

operations = [
migrations.AlterField(
model_name="exam",
name="data_model_version",
field=models.SmallIntegerField(default=3),
),
]
27 changes: 26 additions & 1 deletion kolibri/core/exams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ class Exam(AbstractFacilityDataModel):
"""
The `question_sources` field contains different values depending on the 'data_model_version' field.

V3:
Represents a list of questions of V2 objects each of which are now a "Exam/Quiz Section"
and extends it with an additional description field

# Exam
[
# Section 1
{
"section_title": <section title>,
"description": <section description>,
rtibbles marked this conversation as resolved.
Show resolved Hide resolved
"resource_pool": [ <contentnode_ids of pool of resources> ],
"questions": [
{
"exercise_id": <exercise_pk>,
"question_id": <item_id_within_exercise>,
"title": <title of question>
"counter_in_exercise": <unique_count_for_question>
},
]
},

# Section 2
{...}
]

V2:
Similar to V1, but with a `counter_in_exercise` field
[
Expand Down Expand Up @@ -176,7 +201,7 @@ def save(self, *args, **kwargs):
Certain fields that are only relevant for older model versions get prefixed
with their version numbers.
"""
data_model_version = models.SmallIntegerField(default=2)
data_model_version = models.SmallIntegerField(default=3)

def infer_dataset(self, *args, **kwargs):
return self.cached_related_dataset_lookup("collection")
Expand Down