Repository: learningequality/kolibri

Issue:
 Device > Channels - Channel update process keeps failing
## Observed behavior

Not sure whether this is on Studio's side or on Kolibri's but currently each time I attempt to update a channel I am getting 'Failed' in the task manager. I'm still able to manually import the new resources though.

https://github.com/learningequality/kolibri/assets/79847249/0edf83e5-1b76-451d-a369-4e3269cdea37

## Expected behavior

It should be possible to update a channel.

## Steps to reproduce the issue
1.  Install the [latest Beta 9 build asset](https://github.com/learningequality/kolibri/releases/tag/v0.16.0-beta9).
2. Create a new channel in Studio and import it in Kolibri.
3. In Studio add additional resources the the channel and publish it.
4. In Kolibri go to Device > Channels and attempt to update the channel so that the new resources will be downloaded as well.

## Logs

[UbuntuServerLogs.zip](https://github.com/learningequality/kolibri/files/13460303/UbuntuServerLogs.zip)

## Usage Details
Ubuntu 22  - Chrome


Discussion:
@radinamatic 
@pcenov - is this something that seems to be newly broken with this most recent beta? (I am guessing yes or else there would have been an issue before, but asking just in case)
Hi @marcellamaki - sorry but I can't say when this issue was introduced and I noticed it only as part of the integration testing on Beta 9.

Resolving patch:
diff --git a/kolibri/core/content/utils/channel_import.py b/kolibri/core/content/utils/channel_import.py
--- a/kolibri/core/content/utils/channel_import.py
+++ b/kolibri/core/content/utils/channel_import.py
@@ -240,6 +240,10 @@ def __init__(
     ):
         self.channel_id = channel_id
         self.channel_version = channel_version
+        try:
+            self.current_channel = ChannelMetadata.objects.get(id=self.channel_id)
+        except ChannelMetadata.DoesNotExist:
+            self.current_channel = None
 
         self.cancel_check = cancel_check
 
@@ -951,11 +955,9 @@ def import_channel_data(self):
         return import_ran
 
     def run_and_annotate(self):
-        try:
-            self.current_channel = ChannelMetadata.objects.get(id=self.channel_id)
+        if self.current_channel:
             old_order = self.current_channel.order
-        except ChannelMetadata.DoesNotExist:
-            self.current_channel = None
+        else:
             old_order = None
 
         import_ran = self.import_channel_data()


Test patch:
diff --git a/kolibri/core/content/test/test_channel_import.py b/kolibri/core/content/test/test_channel_import.py
--- a/kolibri/core/content/test/test_channel_import.py
+++ b/kolibri/core/content/test/test_channel_import.py
@@ -62,13 +62,15 @@ class BaseChannelImportClassConstructorTestCase(TestCase):
     """
 
     def test_channel_id(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
-        self.assertEqual(channel_import.channel_id, "test")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
 
     @patch("kolibri.core.content.utils.channel_import.get_content_database_file_path")
     def test_two_bridges(self, db_path_mock, apps_mock, tree_id_mock, BridgeMock):
-        db_path_mock.return_value = "test"
-        ChannelImport("test", "source")
+        idValue = uuid.uuid4().hex
+        db_path_mock.return_value = idValue
+        ChannelImport(idValue, "source")
         BridgeMock.assert_has_calls(
             [
                 call(sqlite_file_path="source"),
@@ -78,7 +80,8 @@ def test_two_bridges(self, db_path_mock, apps_mock, tree_id_mock, BridgeMock):
 
     @patch("kolibri.core.content.utils.channel_import.get_content_database_file_path")
     def test_get_config(self, db_path_mock, apps_mock, tree_id_mock, BridgeMock):
-        ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        ChannelImport(idValue, "")
         apps_mock.assert_has_calls(
             [
                 call.get_app_config("content"),
@@ -87,7 +90,8 @@ def test_get_config(self, db_path_mock, apps_mock, tree_id_mock, BridgeMock):
         )
 
     def test_tree_id(self, apps_mock, tree_id_mock, BridgeMock):
-        ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        ChannelImport(idValue, "")
         tree_id_mock.assert_called_once_with()
 
 
@@ -103,47 +107,65 @@ class BaseChannelImportClassMethodUniqueTreeIdTestCase(TestCase):
 
     def test_empty(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = []
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 1)
 
     def test_one_one(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 2)
 
     def test_one_two(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [2]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 1)
 
     def test_two_one_two(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 2]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 3)
 
     def test_two_one_three(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 3]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 2)
 
     def test_three_one_two_three(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 2, 3]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 4)
 
     def test_three_one_two_four(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 2, 4]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 3)
 
     def test_three_one_three_four(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 3, 4]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 2)
 
     def test_three_one_three_five(self, apps_mock, tree_ids_mock, BridgeMock):
         tree_ids_mock.return_value = [1, 3, 5]
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         self.assertEqual(channel_import.find_unique_tree_id(), 2)
 
 
@@ -156,14 +178,18 @@ class BaseChannelImportClassGenRowMapperTestCase(TestCase):
     """
 
     def test_base_mapper(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         mapper = channel_import.generate_row_mapper()
         record = MagicMock()
         record.test_attr = "test_val"
         self.assertEqual(mapper(record, "test_attr"), "test_val")
 
     def test_column_name_mapping(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         mappings = {"test_attr": "test_attr_mapped"}
         mapper = channel_import.generate_row_mapper(mappings=mappings)
         record = MagicMock()
@@ -171,7 +197,9 @@ def test_column_name_mapping(self, apps_mock, tree_id_mock, BridgeMock):
         self.assertEqual(mapper(record, "test_attr"), "test_val")
 
     def test_method_mapping(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         mappings = {"test_attr": "test_map_method"}
         mapper = channel_import.generate_row_mapper(mappings=mappings)
         record = {}
@@ -181,7 +209,9 @@ def test_method_mapping(self, apps_mock, tree_id_mock, BridgeMock):
         self.assertEqual(mapper(record, "test_attr"), "test_val")
 
     def test_no_column_mapping(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         mappings = {"test_attr": "test_attr_mapped"}
         mapper = channel_import.generate_row_mapper(mappings=mappings)
         record = Mock(spec=["test_attr"])
@@ -198,12 +228,16 @@ class BaseChannelImportClassGenTableMapperTestCase(TestCase):
     """
 
     def test_base_mapper(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         mapper = channel_import.generate_table_mapper()
         self.assertEqual(mapper, channel_import.base_table_mapper)
 
     def test_method_mapping(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         table_map = "test_map_method"
         test_map_method = Mock()
         channel_import.test_map_method = test_map_method
@@ -211,7 +245,9 @@ def test_method_mapping(self, apps_mock, tree_id_mock, BridgeMock):
         self.assertEqual(mapper, test_map_method)
 
     def test_no_column_mapping(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         table_map = "test_map_method"
         with self.assertRaises(AttributeError):
             channel_import.generate_table_mapper(table_map=table_map)
@@ -228,7 +264,9 @@ class BaseChannelImportClassTableImportTestCase(TestCase):
     def test_no_merge_records_bulk_insert_no_flush(
         self, apps_mock, tree_id_mock, BridgeMock
     ):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         record_mock = MagicMock(spec=["__table__"])
         record_mock.__table__.columns.items.return_value = [("test_attr", MagicMock())]
         channel_import.destination.get_class.return_value = record_mock
@@ -240,7 +278,9 @@ def test_no_merge_records_bulk_insert_no_flush(
     def test_no_merge_records_bulk_insert_flush(
         self, apps_mock, tree_id_mock, BridgeMock
     ):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         record_mock = MagicMock(spec=["__table__"])
         record_mock.__table__.columns.items.return_value = [("test_attr", MagicMock())]
         channel_import.destination.get_class.return_value = record_mock
@@ -259,7 +299,9 @@ class BaseChannelImportClassOtherMethodsTestCase(TestCase):
     """
 
     def test_import_channel_methods_called(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         model_mock = Mock(spec=["__name__"])
         channel_import.content_models = [model_mock]
         mapping_mock = Mock()
@@ -283,7 +325,9 @@ def test_import_channel_methods_called(self, apps_mock, tree_id_mock, BridgeMock
             channel_import.execute_post_operations.assert_called_once()
 
     def test_end(self, apps_mock, tree_id_mock, BridgeMock):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         channel_import.end()
         channel_import.destination.end.assert_has_calls([call(), call()])
 
@@ -291,7 +335,9 @@ def test_end(self, apps_mock, tree_id_mock, BridgeMock):
     def test_destination_tree_ids(
         self, select_mock, apps_mock, tree_id_mock, BridgeMock
     ):
-        channel_import = ChannelImport("test", "")
+        idValue = uuid.uuid4().hex
+        channel_import = ChannelImport(idValue, "")
+        self.assertEqual(channel_import.channel_id, idValue)
         class_mock = Mock()
         channel_import.destination.get_class.return_value = class_mock
         channel_import.get_all_destination_tree_ids()