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

Handle partially imported channels in the device plugin #11231

Merged
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
8 changes: 8 additions & 0 deletions kolibri/core/content/test/test_channel_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,14 @@ def test_no_update_old_version(self):
channel.refresh_from_db()
self.assertEqual(channel.version, channel_version)

def test_update_current_partial(self):
channel = ChannelMetadata.objects.first()
channel.partial = True
channel.save()
self.set_content_fixture()
channel.refresh_from_db()
self.assertFalse(channel.partial)

def test_localfile_available_remain_after_import(self):
local_file = LocalFile.objects.get(pk="9f9438fe6b0d42dd8e913d7d04cfb2b2")
local_file.available = True
Expand Down
31 changes: 14 additions & 17 deletions kolibri/core/content/utils/channel_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,37 +714,34 @@ def table_import(self, model, row_mapper, table_mapper):
return result

def check_and_delete_existing_channel(self):
ChannelMetadataTable = self.destination.get_table(ChannelMetadata)
existing_channel = self.destination.execute(
select(ChannelMetadataTable).where(
ChannelMetadataTable.c.id == self.channel_id
)
).fetchone()

if existing_channel:
if self.current_channel:
current_version = self.current_channel.version
current_partial = self.current_channel.partial
if self.partial:
if existing_channel["version"] != self.channel_version:
if current_version != self.channel_version:
# We have previously loaded this channel, with a different version to the metadata we are trying to insert
logger.warning(
(
"Version {channel_version} of channel {channel_id} already exists in database; cancelling partial import of "
+ "version {new_channel_version}"
).format(
channel_version=existing_channel["version"],
channel_version=current_version,
channel_id=self.channel_id,
new_channel_version=self.channel_version,
)
)
return False
return True
elif existing_channel["version"] < self.channel_version:
elif current_version < self.channel_version or current_partial:
# We have an older version of this channel, so let's clean out the old stuff first
# Or we only have a partial import of this channel.
logger.info(
(
"Older version {channel_version} of channel {channel_id} already exists in database; removing old entries "
+ "so we can upgrade to version {new_channel_version}"
).format(
channel_version=existing_channel["version"],
channel_version=current_version,
channel_id=self.channel_id,
new_channel_version=self.channel_version,
)
Expand All @@ -754,21 +751,21 @@ def check_and_delete_existing_channel(self):

root_node = self.destination.execute(
select(ContentNodeTable).where(
ContentNodeTable.c.id == existing_channel["root_id"]
ContentNodeTable.c.id == self.current_channel.root_id
)
).fetchone()

self.delete_old_channel_many_to_many_fields(self.channel_id)
if root_node:
self.delete_old_channel_tree_data(root_node["tree_id"])
else:
# We have previously loaded this channel, with the same or newer version, so our work here is done
# We have previously fully loaded this channel, with the same or newer version, so our work here is done
logger.warning(
(
"Version {channel_version} of channel {channel_id} already exists in database; cancelling import of "
+ "version {new_channel_version}"
).format(
channel_version=existing_channel["version"],
channel_version=current_version,
channel_id=self.channel_id,
new_channel_version=self.channel_version,
)
Expand Down Expand Up @@ -950,10 +947,10 @@ def import_channel_data(self):

def run_and_annotate(self):
try:
old_order = ChannelMetadata.objects.values("order").get(id=self.channel_id)[
"order"
]
self.current_channel = ChannelMetadata.objects.get(id=self.channel_id)
old_order = self.current_channel.order
except ChannelMetadata.DoesNotExist:
self.current_channel = None
old_order = None

import_ran = self.import_channel_data()
Expand Down
11 changes: 10 additions & 1 deletion kolibri/plugins/device/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.db.models import Case
from django.db.models import When
from django_filters.rest_framework import BooleanFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.exceptions import ParseError
Expand Down Expand Up @@ -87,10 +88,18 @@ def to_representation(self, instance):
return value


class DeviceChannelMetadataFilter(ChannelMetadataFilter):
partial = BooleanFilter(field_name="partial", label="Partial")

class Meta:
model = ChannelMetadata
fields = ("partial", "available", "has_exercise")


class DeviceChannelMetadataViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = DeviceChannelMetadataSerializer
filter_backends = (DjangoFilterBackend,)
filter_class = ChannelMetadataFilter
filter_class = DeviceChannelMetadataFilter
permission_classes = (CanManageContent,)

def get_queryset(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import ChannelResource from '../../apiResources/deviceChannel';

// Gets Metadata for Channels whose DBs have been downloaded onto the server.
// Response includes all of the file/resource sizes.
export function getChannelWithContentSizes(channelId) {
export function getChannelWithContentSizes(channelId, filterPartialChannels = true) {
const getParams = {
include_fields: [
'total_resources',
'total_file_size',
'on_device_resources',
'on_device_file_size',
'new_resource_count',
'new_resource_total_size',
],
};
if (filterPartialChannels) {
getParams.partial = false;
}
return new Promise((resolve, reject) => {
ChannelResource.fetchModel({
id: channelId,
getParams: {
include_fields: [
'total_resources',
'total_file_size',
'on_device_resources',
'on_device_file_size',
'new_resource_count',
'new_resource_total_size',
],
},
getParams,
force: true,
}).then(resolve, reject);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function fetchPageData(channelId) {
});
return Promise.all([
getDeviceInfo(),
getChannelWithContentSizes(this.channelId),
getChannelWithContentSizes(channelId, false),
studioChannelPromise,
]).then(([deviceInfo, channel, studioChannel]) => {
return {
Expand Down