1616logger = logging .getLogger (__file__ )
1717
1818
19+ class using_temp_migrated_database :
20+ """
21+ A wrapper context manager for read-only access to a content database
22+ that might not have all current migrations applied. Works by copying
23+ the database to a temporary file, applying migrations to this temporary
24+ database and then using this temporary database.
25+ """
26+
27+ def __init__ (self , database_path ):
28+ self .database_path = database_path
29+ self ._inner_mgr = None
30+
31+ def __enter__ (self ):
32+ self ._named_temporary_file_mgr = tempfile .NamedTemporaryFile (suffix = ".sqlite3" )
33+ self .temp_database_file = self ._named_temporary_file_mgr .__enter__ ()
34+
35+ shutil .copy (self .database_path , self .temp_database_file .name )
36+ self .temp_database_file .seek (0 )
37+
38+ with using_content_database (self .temp_database_file .name ):
39+ # Run migration to handle old content databases published prior to current fields being added.
40+ call_command (
41+ "migrate" ,
42+ app_label = KolibriContentConfig .label ,
43+ database = get_active_content_database (),
44+ )
45+
46+ self ._inner_mgr = using_content_database (self .temp_database_file .name )
47+ self ._inner_mgr .__enter__ ()
48+
49+ def __exit__ (self , exc_type , exc_val , exc_tb ):
50+ self ._inner_mgr .__exit__ (exc_type , exc_val , exc_tb )
51+ self ._named_temporary_file_mgr .__exit__ (exc_type , exc_val , exc_tb )
52+
53+
1954def export_channel_to_kolibri_public (
2055 channel_id ,
2156 channel_version = None ,
@@ -25,36 +60,66 @@ def export_channel_to_kolibri_public(
2560):
2661 logger .info ("Putting channel {} into kolibri_public" .format (channel_id ))
2762
28- if channel_version is not None :
29- db_filename = "{id}-{version}.sqlite3" .format (
63+ versioned_db_filename = "{id}-{version}.sqlite3" .format (
64+ id = channel_id , version = channel_version
65+ )
66+ unversioned_db_filename = "{id}.sqlite3" .format (id = channel_id )
67+
68+ versioned_db_path = storage .path (
69+ os .path .join (settings .DB_ROOT , versioned_db_filename )
70+ )
71+ unversioned_db_path = storage .path (
72+ os .path .join (settings .DB_ROOT , unversioned_db_filename )
73+ )
74+
75+ if channel_version is None :
76+ db_path = unversioned_db_path
77+ else :
78+ db_path = versioned_db_path
79+ _possibly_migrate_unversioned_database (
80+ channel_id = channel_id ,
81+ channel_version = channel_version ,
82+ unversioned_db_path = unversioned_db_path ,
83+ versioned_db_path = versioned_db_path ,
84+ )
85+
86+ with using_temp_migrated_database (db_path ):
87+ channel = ExportedChannelMetadata .objects .get (id = channel_id )
88+ logger .info (
89+ "Found channel {} for id: {} mapping now" .format (channel .name , channel_id )
90+ )
91+ mapper = ChannelMapper (
92+ channel = channel ,
93+ channel_version = channel_version ,
94+ public = public ,
95+ categories = categories ,
96+ countries = countries ,
97+ )
98+ mapper .run ()
99+
100+
101+ def _possibly_migrate_unversioned_database (
102+ channel_id ,
103+ channel_version ,
104+ unversioned_db_path ,
105+ versioned_db_path ,
106+ ):
107+ """
108+ Older channels may only have a single database file and not
109+ versioned databases. If this is the case and the requested channel version
110+ is present in the single database file, this function copies the database to a file
111+ containing the version in the filename.
112+ """
113+ if os .path .exists (versioned_db_path ) or not os .path .exists (unversioned_db_path ):
114+ return
115+
116+ with using_temp_migrated_database (unversioned_db_path ):
117+ contains_requested_version = ExportedChannelMetadata .objects .filter (
30118 id = channel_id , version = channel_version
119+ ).exists ()
120+
121+ if contains_requested_version :
122+ logger .info (
123+ f"Migrating unversioned database { unversioned_db_path } to versioned database { versioned_db_path } "
31124 )
32- else :
33- db_filename = "{id}.sqlite3" .format (id = channel_id )
34- db_location = os .path .join (settings .DB_ROOT , db_filename )
35-
36- with storage .open (db_location ) as storage_file :
37- with tempfile .NamedTemporaryFile (suffix = ".sqlite3" ) as db_file :
38- shutil .copyfileobj (storage_file , db_file )
39- db_file .seek (0 )
40- with using_content_database (db_file .name ):
41- # Run migration to handle old content databases published prior to current fields being added.
42- call_command (
43- "migrate" ,
44- app_label = KolibriContentConfig .label ,
45- database = get_active_content_database (),
46- )
47- channel = ExportedChannelMetadata .objects .get (id = channel_id )
48- logger .info (
49- "Found channel {} for id: {} mapping now" .format (
50- channel .name , channel_id
51- )
52- )
53- mapper = ChannelMapper (
54- channel = channel ,
55- channel_version = channel_version ,
56- public = public ,
57- categories = categories ,
58- countries = countries ,
59- )
60- mapper .run ()
125+ shutil .copy (unversioned_db_path , versioned_db_path )
0 commit comments