1818from django .core .files .storage import default_storage as storage
1919from django .core .management import call_command
2020from django .db .models import Count
21+ from django .db .models import Exists
2122from django .db .models import Max
23+ from django .db .models import OuterRef
2224from django .db .models import Q
25+ from django .db .models import Subquery
2326from django .db .models import Sum
2427from django .db .utils import IntegrityError
2528from django .template .loader import render_to_string
3740from le_utils .constants import roles
3841from past .builtins import basestring
3942from past .utils import old_div
43+ from search .models import ChannelFullTextSearch
44+ from search .models import ContentNodeFullTextSearch
45+ from search .utils import get_fts_annotated_channel_qs
46+ from search .utils import get_fts_annotated_contentnode_qs
4047
4148from contentcuration import models as ccmodels
4249from contentcuration .decorators import delay_user_storage_calculation
@@ -808,6 +815,50 @@ def fill_published_fields(channel, version_notes):
808815 channel .save ()
809816
810817
818+ def sync_contentnode_and_channel_tsvectors (channel_id ):
819+ """
820+ Creates, deletes and updates tsvectors of the channel and all its content nodes
821+ to reflect the current state of channel's main tree.
822+ """
823+ # Update or create channel tsvector entry.
824+ logging .info ("Setting tsvector for channel with id {}." .format (channel_id ))
825+
826+ channel = (get_fts_annotated_channel_qs ()
827+ .values ("keywords_tsvector" , "main_tree__tree_id" )
828+ .get (pk = channel_id ))
829+
830+ obj , is_created = ChannelFullTextSearch .objects .update_or_create (channel_id = channel_id , defaults = {"keywords_tsvector" : channel ["keywords_tsvector" ]})
831+ del obj
832+
833+ if is_created :
834+ logging .info ("Created 1 channel tsvector." )
835+ else :
836+ logging .info ("Updated 1 channel tsvector." )
837+
838+ # Update or create contentnodes tsvector entry for channel_id.
839+ logging .info ("Setting tsvectors for all main tree contentnodes in channel {}." .format (channel_id ))
840+
841+ if ContentNodeFullTextSearch .objects .filter (channel_id = channel_id ).exists ():
842+ # First, delete nodes that are no longer in main_tree.
843+ nodes_no_longer_in_main_tree = ~ Exists (ccmodels .ContentNode .objects .filter (id = OuterRef ("contentnode_id" ), tree_id = channel ["main_tree__tree_id" ]))
844+ ContentNodeFullTextSearch .objects .filter (nodes_no_longer_in_main_tree , channel_id = channel_id ).delete ()
845+
846+ # Now, all remaining nodes are in main_tree, so let's update them.
847+ # Update only changed nodes.
848+ node_tsv_subquery = get_fts_annotated_contentnode_qs (channel_id ).filter (id = OuterRef ("contentnode_id" )).order_by ()
849+ ContentNodeFullTextSearch .objects .filter (channel_id = channel_id , contentnode__complete = True , contentnode__changed = True ).update (
850+ keywords_tsvector = Subquery (node_tsv_subquery .values ("keywords_tsvector" )[:1 ]),
851+ author_tsvector = Subquery (node_tsv_subquery .values ("author_tsvector" )[:1 ])
852+ )
853+
854+ # Insert newly created nodes.
855+ # "set_contentnode_tsvectors" command is defined in "search/management/commands" directory.
856+ call_command ("set_contentnode_tsvectors" ,
857+ "--channel-id={}" .format (channel_id ),
858+ "--tree-id={}" .format (channel ["main_tree__tree_id" ]),
859+ "--complete" )
860+
861+
811862@delay_user_storage_calculation
812863def publish_channel (
813864 user_id ,
@@ -829,8 +880,9 @@ def publish_channel(
829880 set_channel_icon_encoding (channel )
830881 kolibri_temp_db = create_content_database (channel , force , user_id , force_exercises , progress_tracker = progress_tracker )
831882 increment_channel_version (channel )
832- mark_all_nodes_as_published (channel )
833883 add_tokens_to_channel (channel )
884+ sync_contentnode_and_channel_tsvectors (channel_id = channel .id )
885+ mark_all_nodes_as_published (channel )
834886 fill_published_fields (channel , version_notes )
835887
836888 # Attributes not getting set for some reason, so just save it here
0 commit comments