예제 #1
0
 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])
예제 #2
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)
예제 #3
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)
    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)
예제 #4
0
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)
예제 #5
0
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