Skip to content

Commit 9cba74b

Browse files
authored
Merge pull request #5241 from taoerman/community-channels
Update channel logic to support publishing channel draft
2 parents 399bba1 + 225bd38 commit 9cba74b

12 files changed

Lines changed: 163 additions & 50 deletions

File tree

contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@
552552
...mapActions('currentChannel', [
553553
'loadCurrentChannelStagingDiff',
554554
'deployCurrentChannel',
555-
'publishDraftChannel',
555+
'publishStagingChannel',
556556
'reloadCurrentChannelStagingDiff',
557557
]),
558558
...mapActions('currentChannel', { loadCurrentChannel: 'loadChannel' }),
@@ -646,7 +646,7 @@
646646
this.displayPublishDraftDialog = false;
647647
this.isPublishingDraft = true;
648648
649-
this.publishDraftChannel()
649+
this.publishStagingChannel()
650650
.then(() => {
651651
this.isPublishingDraft = false;
652652
this.showSnackbar({

contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ export function publishChannel(context, version_notes) {
5656
}
5757

5858
export function publishDraftChannel(context) {
59-
return Channel.publishDraft(context.state.currentChannelId);
59+
return Channel.publishDraft(context.state.currentChannelId, {use_staging_tree: false});
60+
}
61+
62+
export function publishStagingChannel(context) {
63+
return Channel.publishDraft(context.state.currentChannelId, { use_staging_tree: true });
6064
}
6165

6266
export function channelLanguageExistsInResources(context) {

contentcuration/contentcuration/frontend/shared/data/__tests__/changes.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,15 @@ describe('Change Types', () => {
257257
const change = new PublishedNextChange({
258258
key: '1',
259259
table: TABLE_NAMES.CHANNEL,
260+
use_staging_tree: false,
260261
source: CLIENTID,
261262
});
262263
const rev = await change.saveChange();
263264
const persistedChange = await db[CHANGES_TABLE].get(rev);
264265
expect(persistedChange).toEqual({
265266
rev,
266267
channel_id: change.key,
267-
...pick(change, ['type', 'key', 'table', 'source']),
268+
...pick(change, ['type', 'key', 'table', 'use_staging_tree', 'source']),
268269
});
269270
});
270271

contentcuration/contentcuration/frontend/shared/data/changes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,14 +439,15 @@ export class PublishedChange extends Change {
439439
}
440440

441441
export class PublishedNextChange extends Change {
442-
constructor(fields) {
442+
constructor({ use_staging_tree, ...fields }) {
443443
fields.type = CHANGE_TYPES.PUBLISHED_NEXT;
444444
super(fields);
445445
if (this.table !== TABLE_NAMES.CHANNEL) {
446446
throw TypeError(
447447
`${this.changeType} is only supported by ${TABLE_NAMES.CHANNEL} table but ${this.table} was passed instead`,
448448
);
449449
}
450+
this.setAndValidateBoolean(use_staging_tree, 'use_staging_tree');
450451
this.setChannelAndUserId({ id: this.key });
451452
}
452453
}

contentcuration/contentcuration/frontend/shared/data/resources.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1234,9 +1234,14 @@ export const Channel = new CreateModelResource({
12341234
});
12351235
},
12361236

1237-
publishDraft(id) {
1237+
publishDraft(id, opts={}) {
1238+
const {
1239+
use_staging_tree = false,
1240+
} = opts;
1241+
12381242
const change = new PublishedNextChange({
12391243
key: id,
1244+
use_staging_tree,
12401245
table: this.tableName,
12411246
source: CLIENTID,
12421247
});

contentcuration/contentcuration/frontend/shared/data/serverSync.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const ChangeTypeMapFields = {
3636
'excluded_descendants',
3737
]),
3838
[CHANGE_TYPES.PUBLISHED]: commonFields.concat(['version_notes', 'language']),
39-
[CHANGE_TYPES.PUBLISHED_NEXT]: commonFields,
39+
[CHANGE_TYPES.PUBLISHED_NEXT]: commonFields.concat(['use_staging_tree']),
4040
[CHANGE_TYPES.SYNCED]: commonFields.concat([
4141
'titles_and_descriptions',
4242
'resource_details',

contentcuration/contentcuration/tests/test_exportchannel.py

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -879,12 +879,11 @@ def run_publish_channel(self):
879879
publish_channel(
880880
self.admin_user.id,
881881
self.content_channel.id,
882-
version_notes="",
883882
force=False,
884883
force_exercises=False,
885884
send_email=False,
886885
progress_tracker=None,
887-
language="fr",
886+
is_draft_version=True,
888887
use_staging_tree=True,
889888
)
890889

@@ -894,11 +893,9 @@ def test_none_staging_tree(self):
894893
with self.assertRaises(NoneContentNodeTreeError):
895894
self.run_publish_channel()
896895

897-
def test_staging_tree_published(self):
898-
self.assertFalse(self.content_channel.staging_tree.published)
896+
def test_staging_tree_not_published_for_draft(self):
899897
self.run_publish_channel()
900-
self.content_channel.refresh_from_db()
901-
self.assertTrue(self.content_channel.staging_tree.published)
898+
self.assertFalse(self.content_channel.staging_tree.published)
902899

903900
def test_next_version_exported(self):
904901
self.run_publish_channel()
@@ -928,6 +925,7 @@ def test_staging_tree_used_for_publish(self):
928925
self.admin_user.id,
929926
True,
930927
progress_tracker=None,
928+
is_draft_version=True,
931929
use_staging_tree=True,
932930
)
933931
set_active_content_database(self.tempdb)
@@ -944,3 +942,106 @@ def test_staging_tree_used_for_publish(self):
944942
set_active_content_database(None)
945943
if os.path.exists(self.tempdb):
946944
os.remove(self.tempdb)
945+
946+
class PublishDraftUsingMainTreeTestCase(StudioTestCase):
947+
@classmethod
948+
def setUpClass(cls):
949+
super(PublishDraftUsingMainTreeTestCase, cls).setUpClass()
950+
cls.patch_copy_db = patch("contentcuration.utils.publish.save_export_database")
951+
cls.mock_save_export = cls.patch_copy_db.start()
952+
953+
@classmethod
954+
def tearDownClass(cls):
955+
super(PublishDraftUsingMainTreeTestCase, cls).tearDownClass()
956+
cls.patch_copy_db.stop()
957+
958+
def setUp(self):
959+
super(PublishDraftUsingMainTreeTestCase, self).setUp()
960+
961+
self.channel_version = 3
962+
self.incomplete_video_in_main = "Incomplete video in main tree"
963+
self.complete_video_in_main = "Complete video in main tree"
964+
965+
self.content_channel = channel()
966+
self.content_channel.version = self.channel_version
967+
self.content_channel.save()
968+
969+
# Incomplete node in main_tree should be excluded.
970+
new_node = create_node(
971+
{"kind_id": "video", "title": self.incomplete_video_in_main, "children": []}
972+
)
973+
new_node.complete = False
974+
new_node.parent = self.content_channel.main_tree
975+
new_node.published = False
976+
new_node.save()
977+
978+
# Complete node in main_tree should be included.
979+
new_node = create_node(
980+
{"kind_id": "video", "title": self.complete_video_in_main, "children": []}
981+
)
982+
new_node.complete = True
983+
new_node.parent = self.content_channel.main_tree
984+
new_node.published = False
985+
new_node.save()
986+
987+
def run_publish_channel(self):
988+
publish_channel(
989+
self.admin_user.id,
990+
self.content_channel.id,
991+
force=False,
992+
force_exercises=False,
993+
send_email=False,
994+
progress_tracker=None,
995+
is_draft_version=True,
996+
use_staging_tree=False,
997+
)
998+
999+
def test_next_version_exported(self):
1000+
self.run_publish_channel()
1001+
self.mock_save_export.assert_called_with(
1002+
self.content_channel.id,
1003+
"next",
1004+
True,
1005+
)
1006+
1007+
def test_main_tree_not_impacted(self):
1008+
self.assertFalse(self.content_channel.main_tree.published)
1009+
self.run_publish_channel()
1010+
self.content_channel.refresh_from_db()
1011+
self.assertFalse(self.content_channel.main_tree.published)
1012+
1013+
def test_channel_version_not_incremented(self):
1014+
self.assertEqual(self.content_channel.version, self.channel_version)
1015+
self.run_publish_channel()
1016+
self.content_channel.refresh_from_db()
1017+
self.assertEqual(self.content_channel.version, self.channel_version)
1018+
1019+
def test_main_tree_used_for_publish(self):
1020+
set_channel_icon_encoding(self.content_channel)
1021+
self.tempdb = create_content_database(
1022+
self.content_channel,
1023+
True,
1024+
self.admin_user.id,
1025+
True,
1026+
progress_tracker=None,
1027+
is_draft_version=True,
1028+
use_staging_tree=False,
1029+
)
1030+
set_active_content_database(self.tempdb)
1031+
1032+
nodes = kolibri_models.ContentNode.objects.all()
1033+
self.assertEqual(nodes.filter(title=self.incomplete_video_in_main).count(), 0)
1034+
self.assertEqual(nodes.filter(title=self.complete_video_in_main).count(), 1)
1035+
1036+
cleanup_content_database_connection(self.tempdb)
1037+
set_active_content_database(None)
1038+
if os.path.exists(self.tempdb):
1039+
os.remove(self.tempdb)
1040+
1041+
def test_only_next_file_created(self):
1042+
self.mock_save_export.reset_mock()
1043+
self.run_publish_channel()
1044+
self.assertEqual(self.mock_save_export.call_count, 1)
1045+
call_args = self.mock_save_export.call_args
1046+
self.assertEqual(call_args[0][1], "next")
1047+
self.assertEqual(call_args[0][2], True)

contentcuration/contentcuration/tests/viewsets/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ def generate_publish_channel_event(channel_id):
9494
return event
9595

9696

97-
def generate_publish_next_event(channel_id):
98-
event = base_generate_publish_next_event(channel_id)
97+
def generate_publish_next_event(channel_id, use_staging_tree=False):
98+
event = base_generate_publish_next_event(channel_id, use_staging_tree=use_staging_tree)
9999
event["rev"] = random.randint(1, 10000000)
100100
return event
101101

contentcuration/contentcuration/tests/viewsets/test_channel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ def test_publish_next(self):
500500

501501
self.assertEqual(response.status_code, 200)
502502
modified_channel = models.Channel.objects.get(id=channel.id)
503-
self.assertEqual(modified_channel.staging_tree.published, True)
503+
self.assertEqual(modified_channel.staging_tree.published, False)
504504

505505
def test_publish_next_with_incomplete_staging_tree(self):
506506
channel = testdata.channel()
@@ -515,7 +515,7 @@ def test_publish_next_with_incomplete_staging_tree(self):
515515
channel.save()
516516
self.assertEqual(channel.staging_tree.published, False)
517517

518-
response = self.sync_changes([generate_publish_next_event(channel.id)])
518+
response = self.sync_changes([generate_publish_next_event(channel.id, use_staging_tree=True)])
519519

520520
self.assertEqual(response.status_code, 200)
521521
self.assertTrue(

contentcuration/contentcuration/utils/publish.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,22 @@ def create_content_database(
146146
user_id,
147147
force_exercises,
148148
progress_tracker=None,
149+
is_draft_version=False,
149150
use_staging_tree=False,
150151
):
151152
"""
152153
:type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
153154
"""
155+
if not is_draft_version and use_staging_tree:
156+
raise ValueError("Staging tree is only supported for draft versions")
157+
154158
# increment the channel version
155-
if not use_staging_tree and not force:
159+
if not is_draft_version and not force:
156160
raise_if_nodes_are_all_unchanged(channel)
157161
fh, tempdb = tempfile.mkstemp(suffix=".sqlite3")
158162

159163
with using_content_database(tempdb):
160-
if not use_staging_tree and not channel.main_tree.publishing:
164+
if not is_draft_version and not channel.main_tree.publishing:
161165
channel.mark_publishing(user_id)
162166

163167
call_command(
@@ -183,11 +187,11 @@ def create_content_database(
183187
progress_tracker.track(90)
184188
map_prerequisites(base_tree)
185189
# Need to save as version being published, not current version
186-
version = "next" if use_staging_tree else channel.version + 1
190+
version = "next" if is_draft_version else channel.version + 1
187191
save_export_database(
188192
channel.pk,
189193
version,
190-
use_staging_tree,
194+
is_draft_version,
191195
)
192196
if channel.public:
193197
mapper = ChannelMapper(kolibri_channel)
@@ -1127,14 +1131,14 @@ def mark_all_nodes_as_published(tree):
11271131
logging.info("Marked all nodes as published.")
11281132

11291133

1130-
def save_export_database(channel_id, version, use_staging_tree=False):
1134+
def save_export_database(channel_id, version, is_draft_version=False):
11311135
logging.debug("Saving export database")
11321136
current_export_db_location = get_active_content_database()
11331137
target_paths = [
11341138
os.path.join(settings.DB_ROOT, "{}-{}.sqlite3".format(channel_id, version))
11351139
]
1136-
# Only create non-version path if not using the staging tree
1137-
if not use_staging_tree:
1140+
# Only create non-version path if not is_draft_version
1141+
if not is_draft_version:
11381142
target_paths.append(
11391143
os.path.join(settings.DB_ROOT, "{id}.sqlite3".format(id=channel_id))
11401144
)
@@ -1297,6 +1301,7 @@ def publish_channel( # noqa: C901
12971301
send_email=False,
12981302
progress_tracker=None,
12991303
language=settings.LANGUAGE_CODE,
1304+
is_draft_version=False,
13001305
use_staging_tree=False,
13011306
):
13021307
"""
@@ -1317,23 +1322,23 @@ def publish_channel( # noqa: C901
13171322
user_id,
13181323
force_exercises,
13191324
progress_tracker=progress_tracker,
1320-
use_staging_tree=use_staging_tree,
1325+
is_draft_version=is_draft_version,
1326+
use_staging_tree=use_staging_tree,
13211327
)
13221328
add_tokens_to_channel(channel)
1323-
if not use_staging_tree:
1329+
if not is_draft_version:
13241330
increment_channel_version(channel)
13251331
sync_contentnode_and_channel_tsvectors(channel_id=channel.id)
13261332
mark_all_nodes_as_published(base_tree)
13271333
fill_published_fields(channel, version_notes)
1328-
1329-
# Attributes not getting set for some reason, so just save it here
1330-
base_tree.publishing = False
1331-
base_tree.changed = False
1332-
base_tree.published = True
1333-
base_tree.save()
1334+
base_tree.publishing = False
1335+
base_tree.changed = False
1336+
base_tree.published = True
1337+
base_tree.save()
1338+
13341339

13351340
# Delete public channel cache.
1336-
if not use_staging_tree and channel.public:
1341+
if not is_draft_version and channel.public:
13371342
delete_public_channel_cache_keys()
13381343

13391344
if send_email:
@@ -1355,8 +1360,9 @@ def publish_channel( # noqa: C901
13551360
finally:
13561361
if kolibri_temp_db and os.path.exists(kolibri_temp_db):
13571362
os.remove(kolibri_temp_db)
1358-
base_tree.publishing = False
1359-
base_tree.save()
1363+
if not is_draft_version:
1364+
base_tree.publishing = False
1365+
base_tree.save()
13601366

13611367
elapsed = time.time() - start
13621368

0 commit comments

Comments
 (0)