77import sqlite3
88
99from django .db import connection
10+ from django .db import transaction
11+ from django .db .models import IntegerField
12+ from django .db .models import Sum
13+ from django .db .models .functions import Cast
1014from le_utils .constants import content_kinds
1115from le_utils .constants import library as library_constants
12- from sqlalchemy import and_
13- from sqlalchemy import cast
14- from sqlalchemy import exists
15- from sqlalchemy import func
16- from sqlalchemy import Integer
17- from sqlalchemy import select
16+
17+ # import_channel_from_local_db still goes through channel_import.py, which raises
18+ # SQLAlchemy errors.
1819from sqlalchemy .exc import DatabaseError
1920
2021from kolibri .core .auth .models import FacilityDataset
21- from kolibri .core .content .apps import KolibriContentConfig
2222from kolibri .core .content .constants .kind_to_learningactivity import kind_activity_map
2323from kolibri .core .content .kolibri_plugin import synchronize_content_requests
2424from kolibri .core .content .models import ChannelMetadata
2727from kolibri .core .content .models import LocalFile
2828from kolibri .core .content .tasks import backfill_content_request_priority
2929from kolibri .core .content .tasks import enqueue_automatic_resource_import_if_needed
30+ from kolibri .core .content .utils .annotation import available_children_rollup
3031from kolibri .core .content .utils .annotation import calculate_included_languages
3132from kolibri .core .content .utils .annotation import calculate_ordered_categories
3233from kolibri .core .content .utils .annotation import calculate_ordered_grade_levels
34+ from kolibri .core .content .utils .annotation import has_available_children
3335from kolibri .core .content .utils .annotation import set_channel_ancestors
3436from kolibri .core .content .utils .annotation import set_content_visibility_from_disk
3537from kolibri .core .content .utils .channel_import import FutureSchemaError
4446from kolibri .core .content .utils .search import annotate_label_bitmasks
4547from kolibri .core .content .utils .search import annotate_modality
4648from kolibri .core .content .utils .search import get_all_contentnode_label_metadata
47- from kolibri .core .content .utils .sqlalchemybridge import Bridge
4849from kolibri .core .content .utils .tree import get_channel_node_depth
4950from kolibri .core .device .models import ContentCacheKey
5051from kolibri .core .upgrade import version_upgrade
@@ -145,6 +146,27 @@ def fix_multiple_trees_with_tree_id1():
145146 )
146147
147148
149+ def _rollup_over_available_children (field , leaf_value ):
150+ """
151+ Set field on every leaf from leaf_value, then sum it up the tree.
152+ """
153+ with transaction .atomic ():
154+ ContentNode .objects .exclude (kind = content_kinds .TOPIC ).update (
155+ ** {field : leaf_value }
156+ )
157+
158+ for channel_id in ChannelMetadata .objects .all ().values_list ("id" , flat = True ):
159+ # Go from the deepest level to the shallowest
160+ for level in range (get_channel_node_depth (channel_id ), 0 , - 1 ):
161+ ContentNode .objects .filter (
162+ level = level - 1 , channel_id = channel_id , kind = content_kinds .TOPIC
163+ ).filter (
164+ # A sum over no available children is NULL, so leave those
165+ # topics at the value they already carry.
166+ has_available_children ()
167+ ).update (** {field : available_children_rollup (Sum (field ))})
168+
169+
148170# This was introduced in 0.12.4, so only annotate
149171# when upgrading from versions prior to this.
150172@version_upgrade (old_version = "<0.12.4" )
@@ -153,73 +175,12 @@ def update_num_coach_contents():
153175 Function to set num_coach_content on all topic trees to account for
154176 those that were imported before annotations were performed
155177 """
156- bridge = Bridge (app_name = KolibriContentConfig .label )
157-
158- ContentNodeTable = bridge .get_table (ContentNode )
159-
160- connection = bridge .get_connection ()
161-
162- child = ContentNodeTable .alias ()
163-
164178 logger .info ("Updating num_coach_content on existing channels" )
165179
166- # start a transaction
167-
168- trans = connection .begin ()
169-
170- # Update all leaf ContentNodes to have num_coach_content to 1 or 0
171- connection .execute (
172- ContentNodeTable .update ()
173- .where (
174- # That are not topics
175- ContentNodeTable .c .kind != content_kinds .TOPIC
176- )
177- .values (num_coach_contents = cast (ContentNodeTable .c .coach_content , Integer ()))
178- )
179-
180- # Expression to capture all available child nodes of a contentnode
181- available_nodes = select (child .c .available ).where (
182- and_ (
183- child .c .available == True , # noqa
184- ContentNodeTable .c .id == child .c .parent_id ,
185- )
180+ _rollup_over_available_children (
181+ "num_coach_contents" , Cast ("coach_content" , IntegerField ())
186182 )
187183
188- # Expression that sums the total number of coach contents for each child node
189- # of a contentnode
190- coach_content_num = select (func .sum (child .c .num_coach_contents )).where (
191- and_ (
192- child .c .available == True , # noqa
193- ContentNodeTable .c .id == child .c .parent_id ,
194- )
195- )
196-
197- for channel_id in ChannelMetadata .objects .all ().values_list ("id" , flat = True ):
198- node_depth = get_channel_node_depth (channel_id )
199-
200- # Go from the deepest level to the shallowest
201- for level in range (node_depth , 0 , - 1 ):
202- # Only modify topic availability here
203- connection .execute (
204- ContentNodeTable .update ()
205- .where (
206- and_ (
207- ContentNodeTable .c .level == level - 1 ,
208- ContentNodeTable .c .channel_id == channel_id ,
209- ContentNodeTable .c .kind == content_kinds .TOPIC ,
210- )
211- )
212- # Because we have set availability to False on all topics as a starting point
213- # we only need to make updates to topics with available children.
214- .where (exists (available_nodes ))
215- .values (num_coach_contents = coach_content_num .scalar_subquery ())
216- )
217-
218- # commit the transaction
219- trans .commit ()
220-
221- bridge .end ()
222-
223184
224185# This was introduced in 0.13.0, so only annotate
225186# when upgrading from versions prior to this.
@@ -229,73 +190,12 @@ def update_on_device_resources():
229190 Function to set on_device_resource on all topic trees to account for
230191 those that were imported before annotations were performed
231192 """
232- bridge = Bridge (app_name = KolibriContentConfig .label )
233-
234- ContentNodeTable = bridge .get_table (ContentNode )
235-
236- connection = bridge .get_connection ()
237-
238- child = ContentNodeTable .alias ()
239-
240193 logger .info ("Updating on_device_resource on existing channels" )
241194
242- # start a transaction
243-
244- trans = connection .begin ()
245-
246- # Update all leaf ContentNodes to have on_device_resource to 1 or 0
247- connection .execute (
248- ContentNodeTable .update ()
249- .where (
250- # That are not topics
251- ContentNodeTable .c .kind != content_kinds .TOPIC
252- )
253- .values (on_device_resources = cast (ContentNodeTable .c .available , Integer ()))
254- )
255-
256- # Expression to capture all available child nodes of a contentnode
257- available_nodes = select (child .c .available ).where (
258- and_ (
259- child .c .available == True , # noqa
260- ContentNodeTable .c .id == child .c .parent_id ,
261- )
262- )
263-
264- # Expression that sums the total number of coach contents for each child node
265- # of a contentnode
266- on_device_num = select (func .sum (child .c .on_device_resources )).where (
267- and_ (
268- child .c .available == True , # noqa
269- ContentNodeTable .c .id == child .c .parent_id ,
270- )
195+ _rollup_over_available_children (
196+ "on_device_resources" , Cast ("available" , IntegerField ())
271197 )
272198
273- for channel_id in ChannelMetadata .objects .all ().values_list ("id" , flat = True ):
274- node_depth = get_channel_node_depth (channel_id )
275-
276- # Go from the deepest level to the shallowest
277- for level in range (node_depth , 0 , - 1 ):
278- # Only modify topic availability here
279- connection .execute (
280- ContentNodeTable .update ()
281- .where (
282- and_ (
283- ContentNodeTable .c .level == level - 1 ,
284- ContentNodeTable .c .channel_id == channel_id ,
285- ContentNodeTable .c .kind == content_kinds .TOPIC ,
286- )
287- )
288- # Because we have set availability to False on all topics as a starting point
289- # we only need to make updates to topics with available children.
290- .where (exists (available_nodes ))
291- .values (on_device_resources = on_device_num )
292- )
293-
294- # commit the transaction
295- trans .commit ()
296-
297- bridge .end ()
298-
299199
300200# This was introduced in 0.15.0, so only annotate
301201# when upgrading from versions prior to this.
0 commit comments