def test_convert_posts(self): blog_id = str(self.blogs_ids[0]) blog = Blog(blog_id) kwargs = { 'ordering': 'newest_first', 'page': 1, 'highlight': 0, 'sticky': 0, 'limit': 25 } response_data = blog.posts(wrap=True, **kwargs) results_data = convert_posts(response_data, blog) self.assertIsNotNone(results_data, True) # test meta info _meta_data = results_data.get('_meta') self.assertIsNotNone(_meta_data, True) self.assertEqual(_meta_data.get('max_results'), kwargs.get('limit')) self.assertEqual( _meta_data.get('last_updated_post').get('_id'), self.blogs_list[0].get('last_updated_post').get('_id')) # test results contains _items list _items = results_data.get('_items') self.assertIsNotNone(_items, True) # test blog id self.assertEqual(_items[0].get('blog'), self.blogs_ids[0]) # _items contains post items list post_item = _items[0].get('items') self.assertIsNotNone(post_item, True) self.assertEqual(post_item[0].get('_id'), self.items_ids[0])
def get_blog_posts(blog_id): blog = Blog(blog_id) kwargs = {} # Get boolean arguments and cast string values to bool. try: kwargs['sticky'] = strtobool(request.args.get('sticky', '0')) kwargs['highlight'] = strtobool(request.args.get('highlight', '0')) except ValueError as e: return api_error(str(e), 403) # Get default ordering. ordering = request.args.get('ordering', Blog.default_ordering) if ordering not in Blog.ordering: return api_error('"{}" is not valid'.format(ordering), 403) kwargs['ordering'] = ordering # Get page & limit. try: kwargs['page'] = int(request.args.get('page', Blog.default_page)) kwargs['limit'] = int(request.args.get('limit', Blog.default_page_limit)) except ValueError as e: return api_error(str(e), 403) # Check page value. if kwargs['page'] < 1: return api_error('"page" value is not valid.', 403) # Check max page limit. if kwargs['limit'] > Blog.max_page_limit: return api_error('"limit" value is not valid.', 403) response_data = blog.posts(wrap=True, **kwargs) result_data = convert_posts(response_data, blog) return api_response(result_data, 200)
def get_blog_posts(blog_id): blog = Blog(blog_id) kwargs = {} # Get boolean arguments and cast string values to bool. try: kwargs['sticky'] = strtobool(request.args.get('sticky', '0')) kwargs['highlight'] = strtobool(request.args.get('highlight', '0')) except ValueError as e: return api_error(str(e), 403) # Get default ordering. ordering = request.args.get('ordering', Blog.default_ordering) if ordering not in Blog.ordering: return api_error('"{}" is not valid'.format(ordering), 403) kwargs['ordering'] = ordering # Get page & limit. try: kwargs['page'] = int(request.args.get('page', Blog.default_page)) kwargs['limit'] = int( request.args.get('limit', Blog.default_page_limit)) except ValueError as e: return api_error(str(e), 403) # Check page value. if kwargs['page'] < 1: return api_error('"page" value is not valid.', 403) # Check max page limit. if kwargs['limit'] > Blog.max_page_limit: return api_error('"limit" value is not valid.', 403) response_data = blog.posts(wrap=True, **kwargs) fields = [ '_id', '_etag', '_created', '_updated', 'blog', 'lb_highlight', 'sticky', 'deleted', 'post_status', 'published_date', 'unpublished_date' ] # Convert posts for i, post in enumerate(response_data['_items']): doc = {k: post.get(k) for k in fields} # add items in post doc['items'] = [] for g in post.get('groups', []): if g['id'] != 'main': continue for item in g['refs']: doc['items'].append(_get_converted_item(item['item'])) # add authorship publisher = {} publisher['display_name'] = post['publisher']['display_name'] publisher['picture_url'] = post['publisher'].get('picture_url', '') doc['publisher'] = publisher response_data['_items'][i] = doc # Add additional blog metadata to response _meta. response_data['_meta']['last_updated_post'] = blog._blog.get( 'last_updated_post') response_data['_meta']['last_created_post'] = blog._blog.get( 'last_created_post') return api_response(response_data, 200)
def embed(blog_id, theme=None, output=None, api_host=None): api_host = api_host or request.url_root blog = get_resource_service('client_blogs').find_one(req=None, _id=blog_id) if not blog: return 'blog not found', 404 # if the `output` is the `_id` get the data. if output: if isinstance(output, str): output = get_resource_service('outputs').find_one(req=None, _id=output) if not output: return 'output not found', 404 else: collection = get_resource_service('collections').find_one( req=None, _id=output.get('collection')) output['collection'] = collection # Retrieve picture url from relationship. if blog.get('picture', None): blog['picture'] = get_resource_service('archive').find_one( req=None, _id=blog['picture']) # Retrieve the wanted theme and add it to blog['theme'] if is not the registered one. try: theme_name = request.args.get('theme', theme) except RuntimeError: # This method can be called outside from a request context. theme_name = theme blog_preferences = blog.get('blog_preferences') if blog_preferences is None: return 'blog preferences are not available', 404 blog_theme_name = blog_preferences.get('theme') if not theme_name: # No theme specified. Fallback to theme in blog_preferences. theme_name = blog_theme_name theme = get_resource_service('themes').find_one(req=None, name=theme_name) if theme is None: raise SuperdeskApiError.badRequestError( message= 'You will be able to access the embed after you register the themes' ) try: assets, template_content = collect_theme_assets(theme, parents=[]) except UnknownTheme as e: return str(e), 500 if not template_content: logger.error('Template file not found for theme "%s". Theme: %s' % (theme_name, theme)) return 'Template file not found', 500 theme_service = get_resource_service('themes') # Compute the assets root. if theme.get('public_url', False): assets_root = theme.get('public_url') else: assets_root = theme_service.get_theme_assets_url(theme_name) theme_settings = theme_service.get_default_settings(theme) i18n = theme.get('i18n', {}) # Check if theme is SEO and/or AMP compatible. is_amp = theme.get('ampTheme', False) is_seo = theme.get('seoTheme', False) if is_seo: # Fetch initial blog posts for SEO theme blog_instance = Blog(blog) page_limit = theme_settings.get('postsPerPage', 10) sticky_limit = theme_settings.get('stickyPostsPerPage', 10) ordering = theme_settings.get('postOrder', blog_instance.default_ordering) posts = blog_instance.posts(wrap=True, limit=page_limit, ordering=ordering, deleted=is_amp) sticky_posts = blog_instance.posts(wrap=True, limit=sticky_limit, sticky=True, ordering='newest_first', deleted=is_amp) api_response = {'posts': posts, 'stickyPosts': sticky_posts} embed_env = theme_service.get_theme_template_env( theme, loader=CompiledThemeTemplateLoader) embed_template = embed_env.from_string(template_content) template_content = embed_template.render( blog=blog, output=output, options=theme, json_options=bson_dumps(theme), settings=theme_settings, api_response=api_response, assets_root=assets_root, i18n=i18n) async = theme.get('asyncTheme', False) api_host = api_host.replace('//', app.config.get( 'EMBED_PROTOCOL')) if api_host.startswith('//') else api_host api_host = api_host.replace('http://', app.config.get('EMBED_PROTOCOL')) scope = { 'blog': blog, 'settings': theme_settings, 'assets': assets, 'api_host': api_host, 'output': output, 'template': template_content, 'debug': app.config.get('LIVEBLOG_DEBUG'), 'assets_root': assets_root, 'async': async, 'i18n': i18n } if is_amp: # Add AMP compatible css to template context styles = theme.get('files', {}).get('styles', {}).values() if len(styles): scope['amp_style'] = next(iter(styles)) embed_template = 'embed.html' if is_amp: embed_template = 'embed_amp.html' return render_template(embed_template, **scope)
def embed(blog_id, theme=None, output=None, api_host=None): from liveblog.themes import UnknownTheme # adding import here to avoid circular references from liveblog.advertisements.utils import get_advertisements_list from liveblog.advertisements.amp import AdsSettings, inject_advertisements api_host = api_host or request.url_root blog = get_resource_service('client_blogs').find_one(req=None, _id=blog_id) if not blog: return 'blog not found', 404 # if the `output` is the `_id` get the data. if output: if isinstance(output, str): output = get_resource_service('outputs').find_one(req=None, _id=output) if not output: return 'output not found', 404 else: collection = get_resource_service('collections').find_one(req=None, _id=output.get('collection')) output['collection'] = collection # Retrieve picture url from relationship. if blog.get('picture', None): blog['picture'] = get_resource_service('archive').find_one(req=None, _id=blog['picture']) # Retrieve the wanted theme and add it to blog['theme'] if is not the registered one. try: theme_name = request.args.get('theme', theme) except RuntimeError: # This method can be called outside from a request context. theme_name = theme blog_preferences = blog.get('blog_preferences') if blog_preferences is None: return 'blog preferences are not available', 404 blog_theme_name = blog_preferences.get('theme') if not theme_name: # No theme specified. Fallback to theme in blog_preferences. theme_name = blog_theme_name theme_service = get_resource_service('themes') theme = theme_service.find_one(req=None, name=theme_name) if theme is None: raise SuperdeskApiError.badRequestError( message='You will be able to access the embed after you register the themes') try: assets, template_content = collect_theme_assets(theme, parents=[]) except UnknownTheme as e: return str(e), 500 if not template_content: logger.warning('Template file not found for theme "%s". Theme: %s' % (theme_name, theme)) return 'Template file not found', 500 # Compute the assets root. if theme.get('public_url', False): assets_root = theme.get('public_url') else: assets_root = theme_service.get_theme_assets_url(theme_name) theme_settings = theme_service.get_default_settings(theme) i18n = theme.get('i18n', {}) # the blog level theme overrides the one in theme level # this way we allow user to enable commenting only for certain blog(s) # or the other way around unset = 'unset' blog_users_can_comment = blog.get('users_can_comment', unset) if blog_users_can_comment != unset: theme_settings['canComment'] = True if blog_users_can_comment == 'enabled' else False # also when blog has been archived, we should disable commenting if blog.get('blog_status') == 'closed': theme_settings['canComment'] = False theme_settings['watermark'] = ACTIVATE_WATERMARK # Check if theme is SEO and/or AMP compatible. is_amp = theme.get('ampTheme', False) is_seo = theme.get('seoTheme', False) if is_seo: # Fetch initial blog posts for SEO theme blog_instance = Blog(blog) page_limit = theme_settings.get('postsPerPage', 10) sticky_limit = theme_settings.get('stickyPostsPerPage', 10) ordering = theme_settings.get('postOrder', blog_instance.default_ordering) # let's get the output channel tags if any tags = [] if output: tags = output.get('tags', []) posts = blog_instance.posts(wrap=True, limit=page_limit, ordering=ordering, deleted=is_amp, tags=tags) sticky_posts = blog_instance.posts(wrap=True, limit=sticky_limit, sticky=True, ordering='newest_first', deleted=is_amp, tags=tags) api_response = { 'posts': posts, 'stickyPosts': sticky_posts } embed_env = theme_service.get_theme_template_env(theme, loader=CompiledThemeTemplateLoader) embed_template = embed_env.from_string(template_content) template_content = embed_template.render( blog=blog, output=output, options=theme, json_options=bson_dumps(theme), settings=theme_settings, api_response=api_response, assets_root=assets_root, i18n=i18n, api_host=api_host ) asyncTheme = theme.get('asyncTheme', False) api_host = api_host.replace('//', app.config.get('EMBED_PROTOCOL')) if api_host.startswith('//') else api_host api_host = api_host.replace('http://', app.config.get('EMBED_PROTOCOL')) scope = { 'blog': blog, 'settings': theme_settings, 'assets': assets, 'api_host': api_host, 'output': output, 'template': template_content, 'debug': app.config.get('LIVEBLOG_DEBUG'), 'assets_root': assets_root, 'async': asyncTheme, 'i18n': i18n, 'hook_urls': bool(TRIGGER_HOOK_URLS) } if is_amp: # Add AMP compatible css to template context styles = theme.get('files', {}).get('styles', {}).values() if len(styles): scope['amp_style'] = next(iter(styles)) embed_template = 'embed_amp.html' if is_amp else 'embed.html' blog_archived = blog['blog_status'] == 'closed' solo_subscription = 'solo' in SUBSCRIPTION_LEVEL if blog_archived and solo_subscription: scope['template'] = render_template('blog-unavailable.html', **scope) scope['assets']['scripts'] = [] response_content = render_template(embed_template, **scope) # TODO: move to somewhere else to simplify this method if is_amp and output and theme.get('supportAdsInjection', False): parsed_content = BeautifulSoup(response_content, 'lxml') ads = get_advertisements_list(output) frequency = output['settings'].get('frequency', 4) order = output['settings'].get('order', 1) ad_template = get_theme_template(theme, 'template-ad-entry.html') ads_settings = AdsSettings( frequency=frequency, order=order, template=ad_template, tombstone_class='hide-item') # let's remove hidden elements initially because they're just garbage # complex validation because `embed` it's also called from outside without request context if not request or request and not request.args.get('amp_latest_update_time', False): hidden_items = parsed_content.find_all('article', class_=ads_settings.tombstone_class) for tag in hidden_items: tag.decompose() styles_tmpl = get_theme_template(theme, 'template-ad-styles.html') amp_style = BeautifulSoup(styles_tmpl.render(frequency=frequency), 'html.parser') style_tag = parsed_content.find('style', attrs={'amp-custom': True}) if style_tag: style_tag.append(amp_style.find('style').contents[0]) inject_advertisements(parsed_content, ads_settings, ads, theme) response_content = parsed_content.prettify() return response_content