Skip to content

Commit 58322e6

Browse files
authored
Merge pull request #5008 from learningequality/hotfixes
Patch release v2025.04.28
2 parents 30ef38e + 347a4df commit 58322e6

File tree

8 files changed

+322
-18
lines changed

8 files changed

+322
-18
lines changed

contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue

+1-4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
class="answer-number"
6464
type="number"
6565
:rules="[numericRule]"
66+
@change="updateAnswerText($event, answerIdx)"
6667
/>
6768
<VTextField v-else :value="answer.answer" class="no-border" type="number" />
6869
</div>
@@ -388,10 +389,6 @@
388389
}
389390
},
390391
updateAnswerText(newAnswerText, answerIdx) {
391-
if (newAnswerText === this.answers[answerIdx].answer) {
392-
return;
393-
}
394-
395392
const updatedAnswers = [...this.answers];
396393
updatedAnswers[answerIdx].answer = newAnswerText;
397394

contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/actions.js

+27-12
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,33 @@ import { isNodeComplete } from 'shared/utils/validation';
33
import db from 'shared/data/db';
44
import { TABLE_NAMES } from 'shared/data/constants';
55

6-
function updateNodeComplete(nodeId, context) {
7-
const node = context.rootGetters['contentNode/getContentNode'](nodeId);
8-
const complete = isNodeComplete({
9-
nodeDetails: node,
10-
assessmentItems: context.getters.getAssessmentItems(nodeId),
11-
files: context.rootGetters['file/getContentNodeFiles'](nodeId),
12-
});
13-
return context.dispatch(
14-
'contentNode/updateContentNode',
15-
{ id: nodeId, complete },
16-
{ root: true }
17-
);
6+
// We implement a retry mechanism to ensure that we wait for retrival of contentnode
7+
// when all the nodes for the
8+
// currently displayed topic in the tree view are reloaded
9+
function updateNodeComplete(nodeId, context, maxTries = 10, delayMs = 100) {
10+
let tries = 0;
11+
12+
function tryUpdate() {
13+
const node = context.rootGetters['contentNode/getContentNode'](nodeId);
14+
if (node) {
15+
const complete = isNodeComplete({
16+
nodeDetails: node,
17+
assessmentItems: context.getters.getAssessmentItems(nodeId),
18+
files: context.rootGetters['file/getContentNodeFiles'](nodeId),
19+
});
20+
return context.dispatch(
21+
'contentNode/updateContentNode',
22+
{ id: nodeId, complete },
23+
{ root: true }
24+
);
25+
} else if (tries < maxTries) {
26+
tries++;
27+
setTimeout(tryUpdate, delayMs);
28+
} else {
29+
console.error(`updateNodeComplete: Node ${nodeId} not found in Vuex after ${maxTries} tries`);
30+
}
31+
}
32+
tryUpdate();
1833
}
1934

2035
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Generated by Django 3.2.24 on 2025-04-17 15:16
2+
from django.db import migrations
3+
from django.db import models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("contentcuration", "0150_bloompub_format_and_preset"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="fileformat",
15+
name="extension",
16+
field=models.CharField(
17+
choices=[
18+
("mp4", "MP4 Video"),
19+
("webm", "WEBM Video"),
20+
("vtt", "VTT Subtitle"),
21+
("mp3", "MP3 Audio"),
22+
("pdf", "PDF Document"),
23+
("jpg", "JPG Image"),
24+
("jpeg", "JPEG Image"),
25+
("png", "PNG Image"),
26+
("gif", "GIF Image"),
27+
("json", "JSON"),
28+
("svg", "SVG Image"),
29+
("perseus", "Perseus Exercise"),
30+
("graphie", "Graphie Exercise"),
31+
("zip", "HTML5 Zip"),
32+
("h5p", "H5P"),
33+
("zim", "ZIM"),
34+
("epub", "ePub Document"),
35+
("bloompub", "Bloom Document"),
36+
("bloomd", "Bloom Document"),
37+
("kpub", "Kolibri HTML5 Article"),
38+
],
39+
max_length=40,
40+
primary_key=True,
41+
serialize=False,
42+
),
43+
),
44+
migrations.AlterField(
45+
model_name="formatpreset",
46+
name="id",
47+
field=models.CharField(
48+
choices=[
49+
("high_res_video", "High Resolution"),
50+
("low_res_video", "Low Resolution"),
51+
("video_thumbnail", "Thumbnail"),
52+
("video_subtitle", "Subtitle"),
53+
("video_dependency", "Video (dependency)"),
54+
("audio", "Audio"),
55+
("audio_thumbnail", "Thumbnail"),
56+
("audio_dependency", "audio (dependency)"),
57+
("document", "Document"),
58+
("epub", "ePub Document"),
59+
("document_thumbnail", "Thumbnail"),
60+
("exercise", "Exercise"),
61+
("exercise_thumbnail", "Thumbnail"),
62+
("exercise_image", "Exercise Image"),
63+
("exercise_graphie", "Exercise Graphie"),
64+
("channel_thumbnail", "Channel Thumbnail"),
65+
("topic_thumbnail", "Thumbnail"),
66+
("html5_zip", "HTML5 Zip"),
67+
("html5_dependency", "HTML5 Dependency (Zip format)"),
68+
("html5_thumbnail", "HTML5 Thumbnail"),
69+
("h5p", "H5P Zip"),
70+
("h5p_thumbnail", "H5P Thumbnail"),
71+
("zim", "Zim"),
72+
("zim_thumbnail", "Zim Thumbnail"),
73+
("qti", "QTI Zip"),
74+
("qti_thumbnail", "QTI Thumbnail"),
75+
("slideshow_image", "Slideshow Image"),
76+
("slideshow_thumbnail", "Slideshow Thumbnail"),
77+
("slideshow_manifest", "Slideshow Manifest"),
78+
("imscp_zip", "IMSCP Zip"),
79+
("bloompub", "Bloom Document"),
80+
("kpub", "Kolibri HTML5 Article"),
81+
],
82+
max_length=150,
83+
primary_key=True,
84+
serialize=False,
85+
),
86+
),
87+
]

contentcuration/contentcuration/viewsets/contentnode.py

+5
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,11 @@ def decode_cursor(self, request):
689689
if value is None:
690690
return None
691691

692+
try:
693+
value = int(value)
694+
except ValueError:
695+
raise ValidationError("lft must be an integer but an invalid value was given.")
696+
692697
return Cursor(offset=0, reverse=False, position=value)
693698

694699
def encode_cursor(self, cursor):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Generated by Django 3.2.24 on 2025-04-17 15:16
2+
from django.db import migrations
3+
from django.db import models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("content", "0022_auto_20240915_1414"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="file",
15+
name="extension",
16+
field=models.CharField(
17+
blank=True,
18+
choices=[
19+
("mp4", "MP4 Video"),
20+
("webm", "WEBM Video"),
21+
("vtt", "VTT Subtitle"),
22+
("mp3", "MP3 Audio"),
23+
("pdf", "PDF Document"),
24+
("jpg", "JPG Image"),
25+
("jpeg", "JPEG Image"),
26+
("png", "PNG Image"),
27+
("gif", "GIF Image"),
28+
("json", "JSON"),
29+
("svg", "SVG Image"),
30+
("perseus", "Perseus Exercise"),
31+
("graphie", "Graphie Exercise"),
32+
("zip", "HTML5 Zip"),
33+
("h5p", "H5P"),
34+
("zim", "ZIM"),
35+
("epub", "ePub Document"),
36+
("bloompub", "Bloom Document"),
37+
("bloomd", "Bloom Document"),
38+
("kpub", "Kolibri HTML5 Article"),
39+
],
40+
max_length=40,
41+
),
42+
),
43+
migrations.AlterField(
44+
model_name="file",
45+
name="preset",
46+
field=models.CharField(
47+
blank=True,
48+
choices=[
49+
("high_res_video", "High Resolution"),
50+
("low_res_video", "Low Resolution"),
51+
("video_thumbnail", "Thumbnail"),
52+
("video_subtitle", "Subtitle"),
53+
("video_dependency", "Video (dependency)"),
54+
("audio", "Audio"),
55+
("audio_thumbnail", "Thumbnail"),
56+
("audio_dependency", "audio (dependency)"),
57+
("document", "Document"),
58+
("epub", "ePub Document"),
59+
("document_thumbnail", "Thumbnail"),
60+
("exercise", "Exercise"),
61+
("exercise_thumbnail", "Thumbnail"),
62+
("exercise_image", "Exercise Image"),
63+
("exercise_graphie", "Exercise Graphie"),
64+
("channel_thumbnail", "Channel Thumbnail"),
65+
("topic_thumbnail", "Thumbnail"),
66+
("html5_zip", "HTML5 Zip"),
67+
("html5_dependency", "HTML5 Dependency (Zip format)"),
68+
("html5_thumbnail", "HTML5 Thumbnail"),
69+
("h5p", "H5P Zip"),
70+
("h5p_thumbnail", "H5P Thumbnail"),
71+
("zim", "Zim"),
72+
("zim_thumbnail", "Zim Thumbnail"),
73+
("qti", "QTI Zip"),
74+
("qti_thumbnail", "QTI Thumbnail"),
75+
("slideshow_image", "Slideshow Image"),
76+
("slideshow_thumbnail", "Slideshow Thumbnail"),
77+
("slideshow_manifest", "Slideshow Manifest"),
78+
("imscp_zip", "IMSCP Zip"),
79+
("bloompub", "Bloom Document"),
80+
("kpub", "Kolibri HTML5 Article"),
81+
],
82+
max_length=150,
83+
),
84+
),
85+
migrations.AlterField(
86+
model_name="localfile",
87+
name="extension",
88+
field=models.CharField(
89+
blank=True,
90+
choices=[
91+
("mp4", "MP4 Video"),
92+
("webm", "WEBM Video"),
93+
("vtt", "VTT Subtitle"),
94+
("mp3", "MP3 Audio"),
95+
("pdf", "PDF Document"),
96+
("jpg", "JPG Image"),
97+
("jpeg", "JPEG Image"),
98+
("png", "PNG Image"),
99+
("gif", "GIF Image"),
100+
("json", "JSON"),
101+
("svg", "SVG Image"),
102+
("perseus", "Perseus Exercise"),
103+
("graphie", "Graphie Exercise"),
104+
("zip", "HTML5 Zip"),
105+
("h5p", "H5P"),
106+
("zim", "ZIM"),
107+
("epub", "ePub Document"),
108+
("bloompub", "Bloom Document"),
109+
("bloomd", "Bloom Document"),
110+
("kpub", "Kolibri HTML5 Article"),
111+
],
112+
max_length=40,
113+
),
114+
),
115+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Generated by Django 3.2.24 on 2025-04-17 15:16
2+
from django.db import migrations
3+
from django.db import models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("kolibri_public", "0005_alter_localfile_extension"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="file",
15+
name="preset",
16+
field=models.CharField(
17+
blank=True,
18+
choices=[
19+
("high_res_video", "High Resolution"),
20+
("low_res_video", "Low Resolution"),
21+
("video_thumbnail", "Thumbnail"),
22+
("video_subtitle", "Subtitle"),
23+
("video_dependency", "Video (dependency)"),
24+
("audio", "Audio"),
25+
("audio_thumbnail", "Thumbnail"),
26+
("audio_dependency", "audio (dependency)"),
27+
("document", "Document"),
28+
("epub", "ePub Document"),
29+
("document_thumbnail", "Thumbnail"),
30+
("exercise", "Exercise"),
31+
("exercise_thumbnail", "Thumbnail"),
32+
("exercise_image", "Exercise Image"),
33+
("exercise_graphie", "Exercise Graphie"),
34+
("channel_thumbnail", "Channel Thumbnail"),
35+
("topic_thumbnail", "Thumbnail"),
36+
("html5_zip", "HTML5 Zip"),
37+
("html5_dependency", "HTML5 Dependency (Zip format)"),
38+
("html5_thumbnail", "HTML5 Thumbnail"),
39+
("h5p", "H5P Zip"),
40+
("h5p_thumbnail", "H5P Thumbnail"),
41+
("zim", "Zim"),
42+
("zim_thumbnail", "Zim Thumbnail"),
43+
("qti", "QTI Zip"),
44+
("qti_thumbnail", "QTI Thumbnail"),
45+
("slideshow_image", "Slideshow Image"),
46+
("slideshow_thumbnail", "Slideshow Thumbnail"),
47+
("slideshow_manifest", "Slideshow Manifest"),
48+
("imscp_zip", "IMSCP Zip"),
49+
("bloompub", "Bloom Document"),
50+
("kpub", "Kolibri HTML5 Article"),
51+
],
52+
max_length=150,
53+
),
54+
),
55+
migrations.AlterField(
56+
model_name="localfile",
57+
name="extension",
58+
field=models.CharField(
59+
blank=True,
60+
choices=[
61+
("mp4", "MP4 Video"),
62+
("webm", "WEBM Video"),
63+
("vtt", "VTT Subtitle"),
64+
("mp3", "MP3 Audio"),
65+
("pdf", "PDF Document"),
66+
("jpg", "JPG Image"),
67+
("jpeg", "JPEG Image"),
68+
("png", "PNG Image"),
69+
("gif", "GIF Image"),
70+
("json", "JSON"),
71+
("svg", "SVG Image"),
72+
("perseus", "Perseus Exercise"),
73+
("graphie", "Graphie Exercise"),
74+
("zip", "HTML5 Zip"),
75+
("h5p", "H5P"),
76+
("zim", "ZIM"),
77+
("epub", "ePub Document"),
78+
("bloompub", "Bloom Document"),
79+
("bloomd", "Bloom Document"),
80+
("kpub", "Kolibri HTML5 Article"),
81+
],
82+
max_length=40,
83+
),
84+
),
85+
]

requirements.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ djangorestframework==3.15.1
55
psycopg2-binary==2.9.5
66
django-js-reverse==0.9.1
77
django-registration==3.4
8-
le-utils==0.2.5
8+
le-utils==0.2.10
99
gunicorn==20.1.0
1010
django-postmark==0.1.6
1111
jsonfield==3.1.0

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ jsonschema==4.17.3
155155
# via -r requirements.in
156156
kombu==5.2.4
157157
# via celery
158-
le-utils==0.2.7
158+
le-utils==0.2.10
159159
# via -r requirements.in
160160
packaging==24.0
161161
# via

0 commit comments

Comments
 (0)