Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 3.2.25 on 2026-07-12 22:39
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("contentcuration", "0167_file_size_bigint_expand"),
]

operations = [
migrations.AlterField(
model_name="assessmentitem",
name="type",
field=models.CharField(
choices=[
("input_question", "Input Question"),
("multiple_selection", "Multiple Selection"),
("single_selection", "Single Selection"),
("free_response", "Free Response"),
("perseus_question", "Perseus Question"),
("QTI", "QTI"),
("true_false", "True/False"),
],
default="multiple_selection",
max_length=50,
),
),
]
16 changes: 16 additions & 0 deletions contentcuration/contentcuration/tests/test_exportchannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from le_utils.constants import exercises
from le_utils.constants import format_presets
from le_utils.constants import modalities
from le_utils.constants.format_presets import RENDERABLE_PRESETS_ORDER
from le_utils.constants.labels import accessibility_categories
from le_utils.constants.labels import learning_activities
from le_utils.constants.labels import levels
Expand Down Expand Up @@ -505,6 +506,21 @@ def test_contentnode_file_size_data(self):
for file in files.prefetch_related("local_file"):
self.assertEqual(file.file_size, file.local_file.file_size)

def test_file_included_presets_renderable(self):
# Every non-supplementary (renderable) exported file carries its own preset bit.
files = kolibri_models.File.objects.filter(supplementary=False)
assert files.count() > 0
for file in files:
expected = 2 ** RENDERABLE_PRESETS_ORDER.index(file.preset)
self.assertEqual(file.included_presets, expected)

def test_file_included_presets_supplementary_null(self):
# Supplementary files (e.g. thumbnails) leave included_presets NULL.
files = kolibri_models.File.objects.filter(supplementary=True)
assert files.count() > 0
for file in files:
self.assertIsNone(file.included_presets)

def test_channel_icon_encoding(self):
self.assertIsNotNone(self.content_channel.icon_encoding)

Expand Down
17 changes: 17 additions & 0 deletions contentcuration/contentcuration/utils/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from le_utils.constants import licenses
from le_utils.constants import modalities
from le_utils.constants import roles
from le_utils.constants.format_presets import RENDERABLE_PRESETS_ORDER
from search.models import ChannelFullTextSearch
from search.models import ContentNodeFullTextSearch
from search.utils import get_fts_annotated_channel_qs
Expand Down Expand Up @@ -666,6 +667,21 @@ def create_associated_file_objects(kolibrinode, ccnode):
},
)

included_presets = None
if not preset.supplementary:
try:
included_presets = 2 ** RENDERABLE_PRESETS_ORDER.index(preset.pk)
except ValueError:
# Renderable preset not in the (append-only) ordering — e.g. a newer
# le-utils preset. Log and leave included_presets NULL for this file
# rather than aborting the whole channel publish.
logging.warning(
"Preset %s missing from RENDERABLE_PRESETS_ORDER; leaving "
"included_presets NULL for file %s",
preset.pk,
ccfilemodel.pk,
)

kolibrimodels.File.objects.create(
pk=ccfilemodel.pk,
checksum=ccfilemodel.checksum,
Expand All @@ -679,6 +695,7 @@ def create_associated_file_objects(kolibrinode, ccnode):
thumbnail=preset.thumbnail,
priority=preset.order,
local_file=kolibrilocalfilemodel,
included_presets=included_presets,
)


Expand Down
3 changes: 3 additions & 0 deletions contentcuration/kolibri_content/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ class File(models.Model):
supplementary = models.BooleanField(default=False)
thumbnail = models.BooleanField(default=False)
priority = models.IntegerField(blank=True, null=True, db_index=True)
# Bitmask of the renderable presets a device needs to render this file,
# including the file's own preset. NULL for supplementary files.
included_presets = models.IntegerField(blank=True, null=True)

class Meta:
abstract = True
Expand Down
5 changes: 4 additions & 1 deletion contentcuration/kolibri_content/constants/schema_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

VERSION_5 = "5"

VERSION_6 = "6"

# List of the content db schema versions, ordered from most recent to least recent.
# When a new schema version is generated, it should be added here, at the top of the list.
CONTENT_DB_SCHEMA_VERSIONS = [
VERSION_6,
VERSION_5,
VERSION_4,
VERSION_3,
Expand All @@ -33,7 +36,7 @@
]

# The latest compatible exported schema version for this version of Kolibri
CONTENT_SCHEMA_VERSION = VERSION_5
CONTENT_SCHEMA_VERSION = VERSION_6

# The version name for the current content schema,
# which may have schema modifications not present in the export schema
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2026-07-12 22:27
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("content", "0023_auto_20250417_1516"),
]

operations = [
migrations.AddField(
model_name="file",
name="included_presets",
field=models.IntegerField(blank=True, null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2026-07-12 22:27
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("kolibri_public", "0008_channelmetadata_categories_bitmask_0"),
]

operations = [
migrations.AddField(
model_name="file",
name="included_presets",
field=models.IntegerField(blank=True, null=True),
),
]
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ djangorestframework==3.15.1
psycopg2-binary==2.9.11
django-js-reverse==0.10.2
django-registration==3.4
le-utils==0.2.17
le-utils==0.2.18
gunicorn==25.1.0
django-postmark==0.1.6
jsonfield==3.1.0
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ langcodes==3.5.1
# via -r requirements.in
latex2mathml==3.78.1
# via -r requirements.in
le-utils==0.2.17
le-utils==0.2.18
# via -r requirements.in
markdown-it-py==4.0.0
# via -r requirements.in
Expand Down
Loading