def create_channel(channel_data, user): """ Set up channel """ # Set up initial channel channel, isNew = Channel.objects.get_or_create(id=channel_data['id']) # Add user as editor if channel is new or channel has no editors # Otherwise, check if user is an editor if isNew or channel.editors.count() == 0: channel.editors.add(user) elif user not in channel.editors.all(): raise SuspiciousOperation( "User is not authorized to edit this channel") channel.name = channel_data['name'] channel.description = channel_data['description'] channel.thumbnail = channel_data['thumbnail'] channel.deleted = False channel.source_id = channel_data.get('source_id') channel.source_domain = channel_data.get('source_domain') channel.source_url = channel_data.get( 'source_domain') if isNew else channel.source_url channel.ricecooker_version = channel_data.get('ricecooker_version') channel.language_id = channel_data.get('language') # older versions of ricecooker won't be sending this field. if 'tagline' in channel_data: channel.tagline = channel_data['tagline'] old_chef_tree = channel.chef_tree is_published = channel.main_tree is not None and channel.main_tree.published # Set up initial staging tree channel.chef_tree = ContentNode.objects.create( title=channel.name, kind_id=content_kinds.TOPIC, sort_order=get_next_sort_order(), published=is_published, content_id=channel.id, node_id=channel.id, source_id=channel.source_id, source_domain=channel.source_domain, extra_fields={'ricecooker_version': channel.ricecooker_version}, ) channel.chef_tree.save() channel.save() # Delete chef tree if it already exists if old_chef_tree and old_chef_tree != channel.staging_tree: # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server with ContentNode.objects.disable_mptt_updates(): garbage_node = get_deleted_chefs_root() old_chef_tree.parent = garbage_node old_chef_tree.title = "Old chef tree for channel {}".format( channel.pk) old_chef_tree.save() return channel # Return new channel
def create_channel(channel_data, user): """ Set up channel """ # Set up initial channel channel, isNew = Channel.objects.get_or_create(id=channel_data['id']) # Add user as editor if channel is new or channel has no editors # Otherwise, check if user is an editor if isNew or channel.editors.count() == 0: channel.editors.add(user) elif user not in channel.editors.all(): raise SuspiciousOperation("User is not authorized to edit this channel") channel.name = channel_data['name'] channel.description = channel_data['description'] channel.thumbnail = channel_data['thumbnail'] channel.deleted = False channel.source_id = channel_data.get('source_id') channel.source_domain = channel_data.get('source_domain') channel.ricecooker_version = channel_data.get('ricecooker_version') channel.language_id = channel_data.get('language') old_chef_tree = channel.chef_tree is_published = channel.main_tree is not None and channel.main_tree.published # Set up initial staging tree channel.chef_tree = ContentNode.objects.create( title=channel.name, kind_id=content_kinds.TOPIC, sort_order=get_next_sort_order(), published=is_published, content_id=channel.id, node_id=channel.id, source_id=channel.source_id, source_domain=channel.source_domain, extra_fields=json.dumps({'ricecooker_version': channel.ricecooker_version}), ) channel.chef_tree.save() channel.save() # Delete chef tree if it already exists if old_chef_tree and old_chef_tree != channel.staging_tree: # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server with ContentNode.objects.disable_mptt_updates(): garbage_node = get_deleted_chefs_root() old_chef_tree.parent = garbage_node old_chef_tree.title = "Old chef tree for channel {}".format(channel.pk) old_chef_tree.save() return channel # Return new channel
def handle(self, *args, **options): try: # Set up variables for restoration process print("\n\n********** STARTING CHANNEL RESTORATION **********") start = datetime.datetime.now() source_id = options['source_id'] target_id = options.get('target') or source_id # Test connection to database print("Connecting to database for channel {}...".format(source_id)) tempf = tempfile.NamedTemporaryFile(suffix=".sqlite3", delete=False) conn = None try: if options.get('download_url'): response = requests.get( '{}/content/databases/{}.sqlite3'.format( options['download_url'], source_id)) for chunk in response: tempf.write(chunk) else: filepath = "/".join( [settings.DB_ROOT, "{}.sqlite3".format(source_id)]) # Check if database exists if not default_storage.exists(filepath): raise IOError("The object requested does not exist.") with default_storage.open(filepath) as fobj: shutil.copyfileobj(fobj, tempf) tempf.close() conn = sqlite3.connect(tempf.name) cursor = conn.cursor() # Start by creating channel print("Creating channel...") channel, root_pk = create_channel(conn, target_id) if options.get('editor'): channel.editors.add( models.User.objects.get(email=options['editor'])) channel.save() # Create root node root = models.ContentNode.objects.create( sort_order=models.get_next_sort_order(), node_id=root_pk, title=channel.name, kind_id=content_kinds.TOPIC, original_channel_id=target_id, source_channel_id=target_id, ) # Create nodes mapping to channel print(" Creating nodes...") with transaction.atomic(): create_nodes(cursor, target_id, root, download_url=options.get('download_url')) # TODO: Handle prerequisites # Delete the previous tree if it exists old_previous = channel.previous_tree if old_previous: old_previous.parent = get_deleted_chefs_root() old_previous.title = "Old previous tree for channel {}".format( channel.pk) old_previous.save() # Save tree to target tree channel.previous_tree = channel.main_tree channel.main_tree = root channel.save() finally: conn and conn.close() tempf.close() os.unlink(tempf.name) # Print stats print("\n\nChannel has been restored (time: {ms})\n".format( ms=datetime.datetime.now() - start)) print("\n\n********** RESTORATION COMPLETE **********\n\n") except EarlyExit as e: logging.warning( "Exited early due to {message}.".format(message=e.message)) self.stdout.write( "You can find your database in {path}".format(path=e.db_path))
def import_channel(source_id, target_id=None, download_url=None, editor=None, logger=None): """ Import a channel from another Studio instance. This can be used to copy online Studio channels into local machines for development, testing, faster editing, or other purposes. :param source_id: The UUID of the channel to import from the source Studio instance. :param target_id: The UUID of the channel on the local instance. Defaults to source_id. :param download_url: The URL of the Studio instance to import from. :param editor: The email address of the user you wish to add as an editor, if any. """ global log if logger: log = logger else: log = logging.getLogger(__name__) # Set up variables for the import process log.info("\n\n********** STARTING CHANNEL IMPORT **********") start = datetime.datetime.now() target_id = target_id or source_id # Test connection to database log.info("Connecting to database for channel {}...".format(source_id)) tempf = tempfile.NamedTemporaryFile(suffix=".sqlite3", delete=False) conn = None try: if download_url: response = requests.get('{}/content/databases/{}.sqlite3'.format( download_url, source_id)) for chunk in response: tempf.write(chunk) else: filepath = "/".join( [settings.DB_ROOT, "{}.sqlite3".format(source_id)]) # Check if database exists if not default_storage.exists(filepath): raise IOError("The object requested does not exist.") with default_storage.open(filepath) as fobj: shutil.copyfileobj(fobj, tempf) tempf.close() conn = sqlite3.connect(tempf.name) cursor = conn.cursor() # Start by creating channel log.info("Creating channel...") channel, root_pk = create_channel(conn, target_id) if editor: channel.editors.add(models.User.objects.get(email=editor)) channel.save() # Create root node root = models.ContentNode.objects.create( sort_order=models.get_next_sort_order(), node_id=root_pk, title=channel.name, kind_id=content_kinds.TOPIC, original_channel_id=target_id, source_channel_id=target_id, ) # Create nodes mapping to channel log.info(" Creating nodes...") with transaction.atomic(): create_nodes(cursor, target_id, root, download_url=download_url) # TODO: Handle prerequisites # Delete the previous tree if it exists old_previous = channel.previous_tree if old_previous: old_previous.parent = get_deleted_chefs_root() old_previous.title = "Old previous tree for channel {}".format( channel.pk) old_previous.save() # Save tree to target tree channel.previous_tree = channel.main_tree channel.main_tree = root channel.save() finally: conn and conn.close() tempf.close() os.unlink(tempf.name) # Print stats log.info("\n\nChannel has been imported (time: {ms})\n".format( ms=datetime.datetime.now() - start)) log.info("\n\n********** IMPORT COMPLETE **********\n\n")