def export_channel_task(self, user_id, channel_id, version_notes="", language=settings.LANGUAGE_CODE): with translation.override(language): channel = publish_channel( user_id, channel_id, version_notes=version_notes, send_email=True, progress_tracker=self.progress, ) return { "changes": [ generate_update_event( channel_id, CHANNEL, { "published": True, "primary_token": channel.get_human_token().token }), generate_update_event(channel.main_tree.pk, CONTENTNODE, { "published": True, "changed": False }), ] }
def test_update_files(self): file1 = models.File.objects.create(**self.file_db_metadata) file2 = models.File.objects.create(**self.file_db_metadata) new_preset = format_presets.VIDEO_HIGH_RES self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( file1.id, FILE, {"preset": new_preset}, ), generate_update_event( file2.id, FILE, {"preset": new_preset}, ), ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual( models.File.objects.get(id=file1.id).preset_id, new_preset, ) self.assertEqual( models.File.objects.get(id=file2.id).preset_id, new_preset, )
def test_viewer_cannot_update_some_channels(self): user = testdata.user() channel1 = models.Channel.objects.create(**self.channel_metadata) channel1.editors.add(user) channel2 = models.Channel.objects.create(**self.channel_metadata) channel2.viewers.add(user) new_name = "This is not the old name" self.client.force_authenticate(user=user) with self.settings(TEST_ENV=False): # Override test env here to check what will happen in production response = self.client.post( self.sync_url, [ generate_update_event(channel1.id, CHANNEL, {"name": new_name}), generate_update_event(channel2.id, CHANNEL, {"name": new_name}), ], format="json", ) self.assertEqual(response.status_code, 207, response.content) self.assertEqual( models.Channel.objects.get(id=channel1.id).name, new_name) self.assertNotEqual( models.Channel.objects.get(id=channel2.id).name, new_name)
def test_update_channelsets(self): channelset1 = models.ChannelSet.objects.create( **self.channelset_db_metadata) channelset1.editors.add(self.user) channelset2 = models.ChannelSet.objects.create( **self.channelset_db_metadata) channelset2.editors.add(self.user) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( channelset1.id, CHANNELSET, {"channels": {}}, ), generate_update_event( channelset2.id, CHANNELSET, {"channels": {}}, ), ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertFalse( models.ChannelSet.objects.get( id=channelset1.id).secret_token.channels.filter( pk=self.channel.id).exists()) self.assertFalse( models.ChannelSet.objects.get( id=channelset2.id).secret_token.channels.filter( pk=self.channel.id).exists())
def test_update_clipboard_extra_fields(self): clipboard = models.ContentNode.objects.create( **self.clipboard_db_metadata) node_id1 = uuid.uuid4().hex self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( clipboard.id, CLIPBOARD, { "extra_fields.excluded_descendants.{}".format(node_id1): True }, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertTrue( models.ContentNode.objects.get(id=clipboard.id). extra_fields["excluded_descendants"][node_id1]) node_id2 = uuid.uuid4().hex response = self.client.post( self.sync_url, [ generate_update_event( clipboard.id, CLIPBOARD, { "extra_fields.excluded_descendants.{}".format(node_id2): True }, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertTrue( models.ContentNode.objects.get(id=clipboard.id). extra_fields["excluded_descendants"][node_id1]) self.assertTrue( models.ContentNode.objects.get(id=clipboard.id). extra_fields["excluded_descendants"][node_id2])
def test_update_assessmentitem_remove_file(self): assessmentitem = models.AssessmentItem.objects.create( **self.assessmentitem_db_metadata ) image_file = testdata.fileobj_exercise_image() image_file.assessment_item = assessmentitem image_file.save() question = "A different question" self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( [assessmentitem.contentnode_id, assessmentitem.assessment_id], ASSESSMENTITEM, {"question": question}, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) try: assessmentitem.files.get() self.fail("File was not removed") except models.File.DoesNotExist: pass
def test_update_assessmentitem_with_file_no_permissions(self): assessmentitem = models.AssessmentItem.objects.create( **self.assessmentitem_db_metadata ) image_file = testdata.fileobj_exercise_image() question = "![alt_text](${}/{}.{})".format( exercises.IMG_PLACEHOLDER, image_file.checksum, image_file.file_format_id ) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( [assessmentitem.contentnode_id, assessmentitem.assessment_id], ASSESSMENTITEM, {"question": question}, ) ], format="json", ) self.assertEqual(response.status_code, 400, response.content) try: file = assessmentitem.files.get() self.assertNotEqual(file.id, image_file.id) self.fail("File was updated") except models.File.DoesNotExist: pass
def activate_channel(channel, user): user.check_channel_space(channel) if channel.previous_tree and channel.previous_tree != channel.main_tree: # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server with models.ContentNode.objects.disable_mptt_updates(): garbage_node = get_deleted_chefs_root() channel.previous_tree.parent = garbage_node channel.previous_tree.title = "Previous tree for channel {}".format( channel.pk) channel.previous_tree.save() channel.previous_tree = channel.main_tree channel.main_tree = channel.staging_tree channel.staging_tree = None channel.save() user.staged_files.all().delete() user.set_space_used() change = generate_update_event( channel.id, CHANNEL, { "root_id": channel.main_tree.id, "staging_root_id": None }, ) return change
def test_update_channelset_channels_no_permission(self): channelset = models.ChannelSet.objects.create( **self.channelset_db_metadata) channelset.editors.add(self.user) channel1 = testdata.channel() channel1.save() self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( channelset.id, CHANNELSET, {"channels.{}".format(channel1.id): True}, ) ], format="json", ) self.assertEqual(response.status_code, 400, response.content) self.assertFalse( models.ChannelSet.objects.get( id=channelset.id).secret_token.channels.filter( id=channel1.id).exists())
def move_nodes_task( self, user_id, channel_id, target_id, node_id, position="last-child", ): node = ContentNode.objects.get(id=node_id) target = ContentNode.objects.get(id=target_id) moved = False attempts = 0 try: while not moved and attempts < 10: try: node.move_to( target, position, ) moved = True except OperationalError as e: if "deadlock detected" in e.args[0]: pass else: raise except Exception as e: report_exception(e) return {"changes": [generate_update_event(node.pk, CONTENTNODE, {"parent": node.parent_id})]}
def create(self, validated_data): bookmark = validated_data.pop("bookmark", None) content_defaults = validated_data.pop("content_defaults", {}) validated_data["content_defaults"] = self.fields["content_defaults"].create( content_defaults ) instance = super(ChannelSerializer, self).create(validated_data) if "request" in self.context: user = self.context["request"].user # This has been newly created so add the current user as an editor instance.editors.add(user) if bookmark: user.bookmarked_channels.add(instance) self.changes.append( generate_update_event( instance.id, CHANNEL, { "root_id": instance.main_tree.id, "created": instance.main_tree.created, "published": instance.main_tree.published, "content_defaults": instance.content_defaults, }, ) ) return instance
def test_update_invitation_accept(self): invitation = models.Invitation.objects.create( **self.invitation_db_metadata) self.client.force_authenticate(user=self.invited_user) response = self.client.post( self.sync_url, [ generate_update_event( invitation.id, INVITATION, {"accepted": True}, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) try: models.Invitation.objects.get(id=invitation.id) except models.Invitation.DoesNotExist: self.fail("Invitation was deleted") self.assertTrue( self.channel.editors.filter(pk=self.invited_user.id).exists()) self.assertTrue( models.Invitation.objects.filter(email=self.invited_user.email, channel=self.channel).exists())
def test_duplicate_nodes_task(self): ids = [] node_ids = [] for i in range(3, 6): node_id = "0000000000000000000000000000000" + str(i) node_ids.append(node_id) node = ContentNode.objects.get(node_id=node_id) ids.append(node.pk) parent_node = ContentNode.objects.get( node_id="00000000000000000000000000000002") tasks = [] for source_id in ids: task_args = { "user_id": self.user.pk, "channel_id": self.channel.pk, "source_id": source_id, "target_id": parent_node.pk, "pk": uuid.uuid4().hex, } task, task_info = create_async_task("duplicate-nodes", self.user, apply_async=False, **task_args) tasks.append((task_args, task_info)) for task_args, task_info in tasks: # progress is retrieved dynamically upon calls to get the task info, so # use an API call rather than checking the db directly for progress. url = reverse("task-detail", kwargs={"task_id": task_info.task_id}) response = self.get(url) assert (response.data["status"] == states.SUCCESS ), "Task failed, exception: {}".format( response.data["metadata"]["error"]["traceback"]) self.assertEqual(response.data["status"], states.SUCCESS) self.assertEqual(response.data["task_type"], "duplicate-nodes") result = response.data["metadata"]["result"] node_id = ContentNode.objects.get(pk=task_args["pk"]).node_id self.assertEqual( result["changes"][0], generate_update_event(task_args["pk"], CONTENTNODE, { COPYING_FLAG: False, "node_id": node_id }), ) parent_node.refresh_from_db() children = parent_node.get_children() for child in children: # make sure the copies are in the results if child.original_source_node_id and child.source_node_id: assert child.original_source_node_id in node_ids assert child.source_node_id in node_ids
def test_update_channels(self): user = testdata.user() channel1 = models.Channel.objects.create(**self.channel_metadata) channel1.editors.add(user) channel2 = models.Channel.objects.create(**self.channel_metadata) channel2.editors.add(user) new_name = "This is not the old name" self.client.force_authenticate(user=user) response = self.client.post( self.sync_url, [ generate_update_event(channel1.id, CHANNEL, {"name": new_name}), generate_update_event(channel2.id, CHANNEL, {"name": new_name}), ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual(models.Channel.objects.get(id=channel1.id).name, new_name) self.assertEqual(models.Channel.objects.get(id=channel2.id).name, new_name)
def test_update_channel_defaults(self): user = testdata.user() channel = models.Channel.objects.create(**self.channel_metadata) channel.editors.add(user) author = "This is not the old author" self.client.force_authenticate(user=user) response = self.client.post( self.sync_url, [ generate_update_event(channel.id, CHANNEL, {"content_defaults.author": author}) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual( models.Channel.objects.get( id=channel.id).content_defaults["author"], author) aggregator = "This is not the old aggregator" self.client.force_authenticate(user=user) response = self.client.post( self.sync_url, [ generate_update_event( channel.id, CHANNEL, {"content_defaults.aggregator": aggregator}) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual( models.Channel.objects.get( id=channel.id).content_defaults["author"], author) self.assertEqual( models.Channel.objects.get( id=channel.id).content_defaults["aggregator"], aggregator, )
def test_update_file_empty(self): file = models.File.objects.create(**self.file_db_metadata) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [generate_update_event( file.id, FILE, {}, )], format="json", ) self.assertEqual(response.status_code, 200, response.content)
def test_update_invitation_empty(self): invitation = models.Invitation.objects.create( **self.invitation_db_metadata) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [generate_update_event( invitation.id, INVITATION, {}, )], format="json", ) self.assertEqual(response.status_code, 200, response.content)
def test_update_assessmentitems(self): assessmentitem1 = models.AssessmentItem.objects.create( **self.assessmentitem_db_metadata ) assessmentitem2 = models.AssessmentItem.objects.create( **self.assessmentitem_db_metadata ) new_question = "{}" self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( [assessmentitem1.contentnode_id, assessmentitem1.assessment_id], ASSESSMENTITEM, {"question": new_question}, ), generate_update_event( [assessmentitem2.contentnode_id, assessmentitem2.assessment_id], ASSESSMENTITEM, {"question": new_question}, ), ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual( models.AssessmentItem.objects.get(id=assessmentitem1.id).question, new_question, ) self.assertEqual( models.AssessmentItem.objects.get(id=assessmentitem2.id).question, new_question, )
def test_viewer_can_bookmark_channel(self): user = testdata.user() channel = models.Channel.objects.create(**self.channel_metadata) channel.viewers.add(user) self.client.force_authenticate(user=user) with self.settings(TEST_ENV=False): # Override test env here to check what will happen in production response = self.client.post( self.sync_url, [generate_update_event(channel.id, CHANNEL, {"bookmark": True})], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists())
def test_update_channelset_empty(self): channelset = models.ChannelSet.objects.create( **self.channelset_db_metadata) channelset.editors.add(self.user) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [generate_update_event( channelset.id, CHANNELSET, {}, )], format="json", ) self.assertEqual(response.status_code, 200, response.content)
def create(self, validated_data): channels = validated_data.pop("channels", []) instance = super(ChannelSetSerializer, self).create(validated_data) for channel in channels: instance.secret_token.channels.add(channel) if "request" in self.context: user = self.context["request"].user # This has been newly created so add the current user as an editor instance.editors.add(user) self.changes.append( generate_update_event( instance.id, CHANNELSET, {"secret_token": instance.secret_token.token}, )) return instance
def copy(self, pk, from_key=None, target=None, position=None, mods=None, excluded_descendants=None, **kwargs): try: target, position = self.validate_targeting_args(target, position) except ValidationError as e: return str(e), None try: source = self.get_queryset().get(pk=from_key) except ContentNode.DoesNotExist: error = ValidationError("Copy source node does not exist") return str(error), [generate_delete_event(pk, CONTENTNODE)] # Affected channel for the copy is the target's channel channel_id = target.channel_id if ContentNode.objects.filter(pk=pk).exists(): error = ValidationError("Copy pk already exists") return str(error), None task_args = { "user_id": self.request.user.id, "channel_id": channel_id, "source_id": source.id, "target_id": target.id, "pk": pk, "mods": mods, "excluded_descendants": excluded_descendants, "position": position, } task, task_info = create_async_task("duplicate-nodes", self.request.user, **task_args) return ( None, [ generate_update_event(pk, CONTENTNODE, {TASK_ID: task_info.task_id}) ], )
def duplicate_nodes_task( self, user_id, channel_id, target_id, source_id, pk=None, position="last-child", mods=None, excluded_descendants=None, ): source = ContentNode.objects.get(id=source_id) target = ContentNode.objects.get(id=target_id) can_edit_source_channel = ContentNode.filter_edit_queryset( ContentNode.objects.filter(id=source_id), user_id=user_id).exists() new_node = None try: new_node = source.copy_to( target, position, pk, mods, excluded_descendants, can_edit_source_channel=can_edit_source_channel, progress_tracker=self.progress, ) except IntegrityError: # This will happen if the node has already been created # Pass for now and just return the updated data # Possible we might want to raise an error here, but not clear # whether this could then be a way to sniff for ids pass changes = [] if new_node is not None: changes.append( generate_update_event(pk, CONTENTNODE, { COPYING_FLAG: False, "node_id": new_node.node_id })) return {"changes": changes}
def test_update_assessmentitem_unwriteable_fields(self): assessmentitem = models.AssessmentItem.objects.create( **self.assessmentitem_db_metadata ) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( [assessmentitem.contentnode_id, assessmentitem.assessment_id], ASSESSMENTITEM, {"not_a_field": "not_a_value"}, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content)
def test_update_channel_thumbnail_encoding(self): user = testdata.user() channel = models.Channel.objects.create(**self.channel_metadata) channel.editors.add(user) new_encoding = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQA" self.client.force_authenticate(user=user) response = self.client.post( self.sync_url, [generate_update_event(channel.id, CHANNEL, { "thumbnail_encoding.base64": new_encoding, "thumbnail_encoding.orientation": 1, "thumbnail_encoding.scale": 0.73602189113443, "thumbnail_encoding.startX": -96.66631072431669, "thumbnail_encoding.startY": -335.58116356397636, })], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual(models.Channel.objects.get(id=channel.id).thumbnail_encoding["base64"], new_encoding)
def test_update_invitation_invited_user_cannot_revoke(self): invitation = models.Invitation.objects.create( **self.invitation_db_metadata) self.client.force_authenticate(user=self.invited_user) response = self.client.post( self.sync_url, [ generate_update_event( invitation.id, INVITATION, {"revoked": True}, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) invitation = models.Invitation.objects.get(id=invitation.id) self.assertFalse(invitation.revoked)
def test_attempt_update_missing_assessmentitem(self): self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event([ self.channel.main_tree.get_descendants() .filter(kind_id=content_kinds.EXERCISE) .first() .id, uuid.uuid4().hex ], ASSESSMENTITEM, {"question": "but why is it missing in the first place?"}, ) ], format="json", ) self.assertEqual(response.status_code, 400, response.content) self.assertEqual(response.data.get("errors")[0].get("error")[0], "Not found")
def test_update_file_no_channel_permission(self): file = models.File.objects.create(**self.file_db_metadata) new_preset = format_presets.VIDEO_HIGH_RES self.channel.editors.remove(self.user) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [generate_update_event( file.id, FILE, {"preset": new_preset}, )], format="json", ) self.assertEqual(response.status_code, 400, response.content) self.assertNotEqual( models.File.objects.get(id=file.id).preset_id, new_preset, )
def test_update_file_no_channel(self): file_metadata = self.file_db_metadata contentnode_id = file_metadata.pop("contentnode_id") file = models.File.objects.create(**file_metadata) self.client.force_authenticate(user=self.user) response = self.client.post( self.sync_url, [ generate_update_event( file.id, FILE, {"contentnode": contentnode_id}, ) ], format="json", ) self.assertEqual(response.status_code, 200, response.content) self.assertEqual( models.File.objects.get(id=file.id).contentnode_id, contentnode_id, )
def test_viewer_cannot_edit_public_bookmarked_channel(self): user = testdata.user() new_name = "This is not the old name" channel = models.Channel.objects.create(**self.channel_metadata) channel.viewers.add(user) channel.public = True channel.save() self.client.force_authenticate(user=user) with self.settings(TEST_ENV=False): # Override test env here to check what will happen in production response = self.client.post( self.sync_url, [generate_update_event(channel.id, CHANNEL, { "bookmark": True, "name": new_name })], format="json", ) self.assertEqual(response.status_code, 400, response.content) self.assertTrue(user.bookmarked_channels.filter(id=channel.id).exists()) self.assertNotEqual(models.Channel.objects.get(id=channel.id).name, new_name)