Skip to content

Commit

Permalink
Add a serializer for content manifest files
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanmccall committed May 24, 2022
1 parent 7136d74 commit 00b383c
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
34 changes: 34 additions & 0 deletions kolibri/core/content/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from kolibri.core.content.models import ContentNode
from kolibri.core.content.models import File
from kolibri.core.content.models import Language
from kolibri.core.content.utils.tree import get_channel_content_selection
from kolibri.core.fields import create_timezonestamp


Expand Down Expand Up @@ -248,3 +249,36 @@ def get_updated_resource(self, instance):

def get_is_leaf(self, instance):
return instance.kind != content_kinds.TOPIC


class ContentManifestChannelSerializer(serializers.ModelSerializer):
include_node_ids = serializers.SerializerMethodField()
exclude_node_ids = serializers.SerializerMethodField()

_content_selection = None

class Meta:
model = ChannelMetadata
fields = (
"id",
"version",
"include_node_ids",
"exclude_node_ids",
)

def get_include_node_ids(self, instance):
include_nodes, _ = self._get_content_selection(instance)
return [node.id for node in include_nodes]

def get_exclude_node_ids(self, instance):
_, exclude_nodes = self._get_content_selection(instance)
return [node.id for node in exclude_nodes]

def _get_content_selection(self, instance):
if not self._content_selection:
self._content_selection = get_channel_content_selection(instance)
return self._content_selection


class ContentManifestSerializer(serializers.Serializer):
channels = serializers.ListField(child=ContentManifestChannelSerializer())
39 changes: 39 additions & 0 deletions kolibri/core/content/utils/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,42 @@ def get_channel_node_depth(bridge, channel_id):
return node_depth[0]

return 0


def get_channel_content_selection(channel_metadata):
include_nodes = list()
exclude_nodes = list()

available_nodes = ContentNode.objects.filter(
channel_id=channel_metadata.id, available=True
).exclude(kind="topic")

available_nodes_queue = [channel_metadata.root]

while len(available_nodes_queue) > 0:
node = available_nodes_queue.pop(0)

# We could add nodes to exclude_nodes when less than half of the
# sibling nodes are missing. However, it is unclear if this would
# be useful.

if node.kind == "topic":
leaf_nodes = _get_leaf_nodes(node)
matching_leaf_nodes = set(leaf_nodes).intersection(available_nodes)
missing_leaf_nodes = set(leaf_nodes).difference(available_nodes)
if len(missing_leaf_nodes) == 0:
assert node not in include_nodes
include_nodes.append(node)
elif len(matching_leaf_nodes) > 0:
available_nodes_queue.extend(node.children.all())
elif node in available_nodes:
assert node not in include_nodes
include_nodes.append(node)

return include_nodes, exclude_nodes


def _get_leaf_nodes(node):
return ContentNode.objects.filter(
lft__gte=node.lft, lft__lte=node.rght, channel_id=node.channel_id
).exclude(kind="topic")

0 comments on commit 00b383c

Please sign in to comment.