示例#1
0
    def test_get_timezone_offset_sydney(self):
        utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Australia/Sydney', utc_dt)
        self.assertEqual(offset, '+1100')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Australia/Sydney', utc_dt)
        self.assertEqual(offset, '+1000')
示例#2
0
    def test_get_timezone_offset_wrong_input(self):
        utc_dt = "test"
        offset = get_timezone_offset("Europe/Prague", utc_dt)
        self.assertEqual(offset, "+0000")

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset("Europe/Sydney", utc_dt)
        self.assertEqual(offset, "+0000")
示例#3
0
    def test_get_timezone_offset_wrong_input(self):
        utc_dt = 'test'
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0000')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Sydney', utc_dt)
        self.assertEqual(offset, '+0000')
    def test_get_timezone_offset_sydney(self):
        utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Australia/Sydney', utc_dt)
        self.assertEqual(offset, '+1100')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Australia/Sydney', utc_dt)
        self.assertEqual(offset, '+1000')
    def test_get_timezone_offset_wrong_input(self):
        utc_dt = 'test'
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0000')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Sydney', utc_dt)
        self.assertEqual(offset, '+0000')
    def test_get_timezone_offset_prague(self):
        utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0100')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0200')
示例#7
0
    def test_get_timezone_offset_prague(self):
        utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0100')

        utc_dt = datetime(2015, 9, 8, 23, 0, 0, 0, pytz.UTC)
        offset = get_timezone_offset('Europe/Prague', utc_dt)
        self.assertEqual(offset, '+0200')
        def get_pre_defined_date_filter(start, end):
            filterList = list()
            filterList.append({
                'range': {
                    'dates.start': {
                        'gte': start,
                        'lt': end,
                        'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())

                    }
                }
            })
            filterList.append({
                'range': {
                    'dates.end': {
                        'gte': start,
                        'lt': end,
                        'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                    }
                }
            })

            filterList.append({
                'and': {
                    'filters': [
                        {
                            'range': {
                                'dates.start': {
                                    'lt': start,
                                    'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                },
                            },
                        },
                        {
                            'range': {
                                'dates.end': {
                                    'gt': end,
                                    'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                },
                            },
                        },
                    ],
                },
            })
            return filterList
示例#9
0
def get_highlighted_items(highlights_id):
    """Get items marked for given highlight and passing date range query."""
    highlight = get_resource_service('highlights').find_one(req=None, _id=highlights_id)
    query = {
        'query': {
            'filtered': {'filter': {'and': [
                {'range': {'versioncreated': {'gte': highlight.get('auto_insert', 'now/d'),
                                              'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE,
                                                                               utcnow())}}},
                {'term': {'highlights': str(highlights_id)}},
            ]}}
        },
        'sort': [
            {'versioncreated': 'desc'},
        ],
        'size': 200
    }
    request = ParsedRequest()
    request.args = {'source': json.dumps(query), 'repo': 'archive,published'}
    return list(get_resource_service('search').get(req=request, lookup=None))
示例#10
0
def get_highlighted_items(highlights_id):
    """Get items marked for given highlight and passing date range query."""
    highlight = get_resource_service('highlights').find_one(req=None,
                                                            _id=highlights_id)
    query = {
        'query': {
            'filtered': {
                'filter': {
                    'and': [
                        {
                            'range': {
                                'versioncreated': {
                                    'gte':
                                    highlight.get('auto_insert', 'now/d'),
                                    'time_zone':
                                    get_timezone_offset(
                                        config.DEFAULT_TIMEZONE, utcnow())
                                }
                            }
                        },
                        {
                            'term': {
                                'highlights': str(highlights_id)
                            }
                        },
                    ]
                }
            }
        },
        'sort': [
            {
                'versioncreated': 'desc'
            },
        ],
        'size': 200
    }
    request = ParsedRequest()
    request.args = {'source': json.dumps(query), 'repo': 'archive,published'}
    return list(get_resource_service('search').get(req=request, lookup=None))
示例#11
0
    def __init__(self,
                 field: str,
                 gt: str = None,
                 gte: str = None,
                 lt: str = None,
                 lte: str = None,
                 value_format: str = None,
                 time_zone: str = None,
                 start_of_week: int = None,
                 date_range: DATE_RANGE = None,
                 date: str = None):
        """Allows to easily set fields by name using kwargs"""

        self.field = field
        self.gt = gt
        self.gte = gte
        self.lt = lt
        self.lte = lte
        self.value_format = value_format
        self.time_zone = time_zone if time_zone else get_timezone_offset(
            app.config['DEFAULT_TIMEZONE'], utcnow())
        self.start_of_week = int(start_of_week or 0)
        self.date_range = date_range
        self.date = str_to_date(date) if date else None
示例#12
0
 def _get_timezone_offset(self, params):
     if params.get('tz_offset') is not None:
         return params.get('tz_offset')
     return get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
示例#13
0
def get_time_zone(params: Dict[str, Any]):
    return params.get('tz_offset') or get_timezone_offset(
        app.config['DEFAULT_TIMEZONE'], utcnow())
    def _get_planning_date_filters(self, request):
        """Get date filters for planning resource

        :param request: object representing the HTTP request
        """
        params = request.args or MultiDict()
        date_filter_param, start_date, end_date = self._parse_date_params(params)
        if not (date_filter_param or start_date or end_date):
            return {
                'nested': {
                    'path': '_planning_schedule',
                    'filter': {
                        'range': {
                            '_planning_schedule.scheduled': {
                                'gte': 'now/d',
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            }
                        }
                    }
                }
            }

        start_of_week = self._get_start_of_week(params)
        date_filters = {
            'range': {
                '_planning_schedule.scheduled': {
                    'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                }
            }
        }

        if date_filter_param.lower() == 'today':
            date_filters['range']['_planning_schedule.scheduled']['gte'] = 'now/d'
            date_filters['range']['_planning_schedule.scheduled']['lt'] = 'now+24h/d'
        elif date_filter_param.lower() == 'tomorrow':
            date_filters['range']['_planning_schedule.scheduled']['gte'] = 'now+24h/d'
            date_filters['range']['_planning_schedule.scheduled']['lt'] = 'now+48h/d'
        elif date_filter_param.lower() == 'this_week':
            end_of_this_week = get_start_of_next_week(None, start_of_week)
            start_of_this_week = end_of_this_week - timedelta(days=7)

            date_filters['range']['_planning_schedule.scheduled']['gte'] = \
                '{}||/d'.format(start_of_this_week.strftime(config.ELASTIC_DATE_FORMAT))
            date_filters['range']['_planning_schedule.scheduled']['lt'] = \
                '{}||/d'.format(end_of_this_week.strftime(config.ELASTIC_DATE_FORMAT))
        elif date_filter_param.lower() == 'next_week':
            start_of_next_week = get_start_of_next_week(None, start_of_week)
            end_of_next_week = start_of_next_week + timedelta(days=7)

            date_filters['range']['_planning_schedule.scheduled']['gte'] = \
                '{}||/d'.format(start_of_next_week.strftime(config.ELASTIC_DATE_FORMAT))
            date_filters['range']['_planning_schedule.scheduled']['lt'] = \
                '{}||/d'.format(end_of_next_week.strftime(config.ELASTIC_DATE_FORMAT))
        else:
            if start_date:
                date_filters['range']['_planning_schedule.scheduled']['gte'] = start_date
            if end_date:
                date_filters['range']['_planning_schedule.scheduled']['lte'] = end_date

        return {
            'nested': {
                'path': '_planning_schedule',
                'filter': date_filters,
            }
        }
    def _get_events_date_filters(self, request):
        """Get date filters for events resource

        :param request: object representing the HTTP request
        """
        params = request.args or MultiDict()
        date_filter_param, start_date, end_date = self._parse_date_params(params)
        if not (date_filter_param or start_date or end_date):
            return {
                'range': {
                    'dates.end': {
                        'gte': 'now/d',
                        'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                    }
                }
            }

        start_of_week = self._get_start_of_week(params)
        date_filters = []

        def get_pre_defined_date_filter(start, end):
            filterList = list()
            filterList.append({
                'range': {
                    'dates.start': {
                        'gte': start,
                        'lt': end,
                        'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())

                    }
                }
            })
            filterList.append({
                'range': {
                    'dates.end': {
                        'gte': start,
                        'lt': end,
                        'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                    }
                }
            })

            filterList.append({
                'and': {
                    'filters': [
                        {
                            'range': {
                                'dates.start': {
                                    'lt': start,
                                    'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                },
                            },
                        },
                        {
                            'range': {
                                'dates.end': {
                                    'gt': end,
                                    'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                },
                            },
                        },
                    ],
                },
            })
            return filterList

        if date_filter_param.lower() == 'today':
            date_filters = get_pre_defined_date_filter('now/d', 'now+24h/d')
        elif date_filter_param.lower() == 'tomorrow':
            date_filters = get_pre_defined_date_filter('now+24h/d', 'now+48h/d')
        elif date_filter_param.lower() == 'this_week':
            end_of_this_week = get_start_of_next_week(None, start_of_week)
            start_of_this_week = end_of_this_week - timedelta(days=7)

            date_filters = get_pre_defined_date_filter(
                '{}||/d'.format(start_of_this_week.strftime(config.ELASTIC_DATE_FORMAT)),
                '{}||/d'.format(end_of_this_week.strftime(config.ELASTIC_DATE_FORMAT))
            )
        elif date_filter_param.lower() == 'next_week':
            start_of_next_week = get_start_of_next_week(None, start_of_week)
            end_of_next_week = start_of_next_week + timedelta(days=7)

            date_filters = get_pre_defined_date_filter(
                '{}||/d'.format(start_of_next_week.strftime(config.ELASTIC_DATE_FORMAT)),
                '{}||/d'.format(end_of_next_week.strftime(config.ELASTIC_DATE_FORMAT))
            )
        else:
            if start_date and not end_date:
                date_filters.extend([
                    {
                        'range': {
                            'dates.start': {
                                'gte': start_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                        },
                    },
                    {
                        'range': {
                            'dates.end': {
                                'gte': start_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                        },
                    }
                ])
            elif not start_date and end_date:
                date_filters.extend([
                    {
                        'range': {
                            'dates.end': {
                                'lte': end_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                        },
                    },
                    {
                        'range': {
                            'dates.start': {
                                'lte': end_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                        },
                    }
                ])
            else:
                date_filters.extend([
                    {
                        'range': {
                            'dates.start': {
                                'gte': start_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                            'dates.end': {
                                'lte': end_date,
                                'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                            },
                        },
                    },
                    {
                        'and': {
                            'filters': [
                                {
                                    'range': {
                                        'dates.start': {
                                            'lt': start_date,
                                            'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                        },
                                    },
                                },
                                {
                                    'range': {
                                        'dates.end': {
                                            'gt': end_date,
                                            'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                        },
                                    },
                                },
                            ],
                        },
                    },
                    {
                        'or': {
                            'filters': [
                                {
                                    'range': {
                                        'dates.start': {
                                            'gte': start_date,
                                            'lte': end_date,
                                            'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                        },
                                    },
                                },
                                {
                                    'range': {
                                        'dates.end': {
                                            'gte': start_date,
                                            'lte': end_date,
                                            'time_zone': get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow())
                                        },
                                    },
                                },
                            ],
                        },
                    }
                ])

        return {
            'or': {
                'filters': date_filters
            }
        }
def generate_text_item(items, template_name, resource_type):
    template = get_resource_service(
        'planning_export_templates').get_export_template(
            template_name, resource_type)
    archive_service = get_resource_service('archive')
    if not template:
        raise SuperdeskApiError.badRequestError('Invalid template selected')

    for item in items:
        # Create list of assignee with preference to coverage_provider, if not, assigned user
        item['published_archive_items'] = []
        item['assignees'] = []
        item['text_assignees'] = []
        item['contacts'] = []
        text_users = []
        text_desks = []
        users = []
        desks = []

        def enhance_coverage(planning, item, users):
            def _enhance_assigned_provider(coverage, item, assigned_to):
                """
                Enhances the text_assignees with the contact details if it's assigned to an external provider
                """
                if assigned_to.get('contact'):
                    provider_contact = get_resource_service(
                        'contacts').find_one(req=None,
                                             _id=assigned_to.get('contact'))

                    assignee_str = "{0} - {1} {2} ".format(
                        assigned_to['coverage_provider']['name'],
                        provider_contact.get('first_name', ''),
                        provider_contact.get('last_name', ''))
                    phone_number = [
                        n.get('number')
                        for n in provider_contact.get('mobile', []) +
                        provider_contact.get('contact_phone', [])
                    ]
                    if len(phone_number):
                        assignee_str += ' ({0})'.format(phone_number[0])

                    # If there is an internal note on the coverage that is different to the internal note
                    # on the planning
                    if (coverage.get('planning', {})).get('internal_note', '') \
                            and item.get('internal_note', '') !=\
                            (coverage.get('planning', {})).get('internal_note', ''):
                        assignee_str += ' ({0})'.format(
                            (coverage.get('planning',
                                          {})).get('internal_note', ''))

                    item['text_assignees'].append(assignee_str)
                else:
                    item['text_assignees'].append(
                        assigned_to['coverage_provider']['name'])

            for c in (planning.get('coverages') or []):
                is_text = c.get('planning', {}).get('g2_content_type',
                                                    '') == 'text'
                completed = (
                    c.get('assigned_to')
                    or {}).get('state') == ASSIGNMENT_WORKFLOW_STATE.COMPLETED
                assigned_to = c.get('assigned_to') or {}
                user = None
                desk = None
                if assigned_to.get('coverage_provider'):
                    item['assignees'].append(
                        assigned_to['coverage_provider']['name'])
                    if is_text and not completed:
                        _enhance_assigned_provider(c, item, assigned_to)
                elif assigned_to.get('user'):
                    user = assigned_to['user']
                    users.append(user)
                elif assigned_to.get('desk'):
                    desk = assigned_to.get('desk')
                    desks.append(desk)

                # Get abstract from related text item if coverage is 'complete'
                if is_text:
                    if completed:
                        results = list(
                            archive_service.get_from_mongo(
                                req=None,
                                lookup={
                                    'assignment_id':
                                    ObjectId(
                                        c['assigned_to']['assignment_id']),
                                    'state': {
                                        '$in': ['published', 'corrected']
                                    },
                                    'pubstatus':
                                    'usable',
                                    'rewrite_of':
                                    None
                                }))
                        if len(results) > 0:
                            item['published_archive_items'].append({
                                'archive_text':
                                get_first_paragraph_text(
                                    results[0].get('abstract')) or '',
                                'archive_slugline':
                                results[0].get('slugline') or ''
                            })
                    elif c.get('news_coverage_status',
                               {}).get('qcode') == 'ncostat:int':
                        if user:
                            text_users.append({
                                'user':
                                user,
                                'note': (c.get('planning', {})).get(
                                    'internal_note', '') if
                                (c.get('planning', {})).get(
                                    'internal_note',
                                    '') != item.get('internal_note') else None
                            })
                        else:
                            text_desks.append(desk)

            item['contacts'] = get_contacts_from_item(item)

        if resource_type == 'planning':
            enhance_coverage(item, item, users)
        else:
            for p in (item.get('plannings') or []):
                enhance_coverage(p, item, users)

        users = get_resource_service('users').find(
            where={'_id': {
                '$in': users
            }})

        desks = get_resource_service('desks').find(
            where={'_id': {
                '$in': desks
            }})

        for u in users:
            name = u.get(
                'display_name', "{0} {1}".format(u.get('first_name'),
                                                 u.get('last_name')))
            item['assignees'].append(name)
            text_user = next(
                (_i for _i in text_users if _i['user'] == str(u.get('_id')))
                or [], None)
            if text_user:
                item['text_assignees'].append(
                    '{0} ({1})'.format(name, text_user.get('note'))
                    if text_user.get('note') else '{0}'.format(name))

        for d in desks:
            item['assignees'].append(d['name'])
            if str(d['_id']) in text_desks:
                item['text_assignees'].append(d['name'])

        set_item_place(item)

        item['description_text'] = item.get('description_text') or (
            item.get('event') or {}).get('definition_short')
        item['slugline'] = item.get('slugline') or (item.get('event')
                                                    or {}).get('name')

        # Handle dates and remote time-zones
        if item.get('dates') or (item.get('event') or {}).get('dates'):
            dates = item.get('dates') or item.get('event').get('dates')
            item['schedule'] = utc_to_local(config.DEFAULT_TIMEZONE,
                                            dates.get('start'))
            if get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow()) !=\
                    get_timezone_offset(dates.get('tz'), utcnow()):
                item['schedule'] = "{} ({})".format(
                    item['schedule'].strftime('%H%M'),
                    item['schedule'].tzname())
            else:
                item['schedule'] = item['schedule'].strftime('%H%M')

    agendas = []
    if resource_type == 'planning':
        agendas = group_items_by_agenda(items)
        inject_internal_converages(items)

        labels = {}
        cv = get_resource_service('vocabularies').find_one(
            req=None, _id='g2_content_type')
        if cv:
            labels = {_type['qcode']: _type['name'] for _type in cv['items']}

        for item in items:
            item['coverages'] = [
                labels.get(
                    coverage.get('planning').get('g2_content_type'),
                    coverage.get('planning').get('g2_content_type')) +
                (' (cancelled)'
                 if coverage.get('workflow_status', '') == 'cancelled' else '')
                for coverage in item.get('coverages', [])
                if (coverage.get('planning') or {}).get('g2_content_type')
            ]

    article = {}

    for key, value in template.items():
        if value.endswith(".html"):
            article[key.replace('_template',
                                '')] = render_template(value,
                                                       items=items,
                                                       agendas=agendas)
        else:
            article[key] = render_template_string(value,
                                                  items=items,
                                                  agendas=agendas)

    return article
示例#17
0
 def get_utc_offset(self):
     return get_timezone_offset(app.config['DEFAULT_TIMEZONE'], utcnow())
def generate_text_item(items, template_name, resource_type):
    template = get_resource_service(
        'planning_export_templates').get_export_template(
            template_name, resource_type)
    if not template:
        raise SuperdeskApiError.badRequestError('Invalid template selected')

    for item in items:
        # Create list of assignee with preference to coverage_provider, if not, assigned user
        item['published_archive_items'] = []
        item['assignees'] = []
        item['text_assignees'] = []
        item['contacts'] = []
        text_users = []
        text_desks = []
        users = []
        desks = []

        if item['type'] == 'planning':
            enhance_coverage(item, item, users, desks, text_users, text_desks)
        else:
            for p in (item.get('plannings') or []):
                enhance_coverage(p, item, users, desks, text_users, text_desks)

        users = get_resource_service('users').find(
            where={'_id': {
                '$in': users
            }})

        desks = get_resource_service('desks').find(
            where={'_id': {
                '$in': desks
            }})

        for u in users:
            name = u.get(
                'display_name', "{0} {1}".format(u.get('first_name'),
                                                 u.get('last_name')))
            item['assignees'].append(name)
            text_user = next(
                (_i for _i in text_users if _i['user'] == str(u.get('_id')))
                or [], None)
            if text_user:
                item['text_assignees'].append(
                    '{0} ({1})'.format(name, text_user.get('note'))
                    if text_user.get('note') else '{0}'.format(name))

        for d in desks:
            item['assignees'].append(d['name'])
            if str(d['_id']) in text_desks:
                item['text_assignees'].append(d['name'])

        set_item_place(item)

        item['description_text'] = item.get('description_text') or (
            item.get('event') or {}).get('definition_short')
        item['slugline'] = item.get('slugline') or (item.get('event')
                                                    or {}).get('name')

        # Handle dates and remote time-zones
        if item.get('dates') or (item.get('event') or {}).get('dates'):
            dates = item.get('dates') or item.get('event').get('dates')
            item['schedule'] = utc_to_local(config.DEFAULT_TIMEZONE,
                                            dates.get('start'))
            if get_timezone_offset(config.DEFAULT_TIMEZONE, utcnow()) !=\
                    get_timezone_offset(dates.get('tz'), utcnow()):
                item['schedule'] = "{} ({})".format(
                    item['schedule'].strftime('%H%M'),
                    item['schedule'].tzname())
            else:
                item['schedule'] = item['schedule'].strftime('%H%M')

    agendas = group_items_by_agenda(items)
    inject_internal_coverages(items)

    labels = {}
    cv = get_resource_service('vocabularies').find_one(req=None,
                                                       _id='g2_content_type')
    if cv:
        labels = {_type['qcode']: _type['name'] for _type in cv['items']}

    for item in items:
        item['coverages'] = [
            labels.get(
                (coverage.get('planning') or {}).get('g2_content_type'),
                (coverage.get('planning') or {}).get('g2_content_type')) +
            (' (cancelled)'
             if coverage.get('workflow_status', '') == 'cancelled' else '')
            for coverage in item.get('coverages', [])
            if (coverage.get('planning') or {}).get('g2_content_type')
        ]

    article = {}

    for key, value in template.items():
        if value.endswith(".html"):
            article[key.replace('_template',
                                '')] = render_template(value,
                                                       items=items,
                                                       agendas=agendas)
        else:
            article[key] = render_template_string(value,
                                                  items=items,
                                                  agendas=agendas)

    return article