def _set_dates(self, agenda_event, tz, start, end): """ Translate the date formats to those used by Agenda :param agenda_event: :param tz: :param start: :param end: :return: """ datefromlocal = utc_to_local(tz, start) datetolocal = utc_to_local(tz, end) if datefromlocal is None: return agenda_event['DateFrom'] = datefromlocal.strftime('%Y-%m-%d') # If the to date is different to the from date if datefromlocal.strftime('%Y-%m-%d') != datetolocal.strftime('%Y-%m-%d'): agenda_event['DateTo'] = datetolocal.strftime('%Y-%m-%d') from_time = datefromlocal.strftime('%H:%M') to_time = datetolocal.strftime('%H:%M') # All day events in Agenda only have a Date if not from_time == '00:00' and not to_time == '24:59' and datefromlocal.strftime( '%Y-%m-%d') == datetolocal.strftime('%Y-%m-%d'): agenda_event['TimeFrom'] = from_time agenda_event['TimeTo'] = to_time # Need to get the UTC offset offset_str = datefromlocal.strftime('%z') agenda_event['TimeFromZone'] = offset_str[0:3] + ':' + offset_str[3:5] offset_str = datetolocal.strftime('%z') agenda_event['TimeToZone'] = offset_str[0:3] + ':' + offset_str[3:5]
def _set_dates(self, agenda_event, tz, start, end): """ Translate the date formats to those used by Agenda :param agenda_event: :param tz: :param start: :param end: :return: """ datefromlocal = utc_to_local(tz, start) datetolocal = utc_to_local(tz, end) if datefromlocal is None: return agenda_event['DateFrom'] = datefromlocal.strftime('%Y-%m-%d') # If the to date is different to the from date if datefromlocal.strftime('%Y-%m-%d') != datetolocal.strftime( '%Y-%m-%d'): agenda_event['DateTo'] = datetolocal.strftime('%Y-%m-%d') from_time = datefromlocal.strftime('%H:%M') to_time = datetolocal.strftime('%H:%M') # All day events in Agenda only have a Date if not from_time == '00:00' and not to_time == '24:59' and datefromlocal.strftime( '%Y-%m-%d') == datetolocal.strftime('%Y-%m-%d'): agenda_event['TimeFrom'] = from_time agenda_event['TimeTo'] = to_time # Need to get the UTC offset offset_str = datefromlocal.strftime('%z') agenda_event['TimeFromZone'] = offset_str[0:3] + ':' + offset_str[3:5] offset_str = datetolocal.strftime('%z') agenda_event['TimeToZone'] = offset_str[0:3] + ':' + offset_str[3:5]
def update_recurring_events(self, updates, original, update_method): historic, past, future = self.get_recurring_timeline(original) # Determine if the selected event is the first one, if so then # act as if we're changing future events if len(historic) == 0 and len(past) == 0: update_method = UPDATE_FUTURE if update_method == UPDATE_FUTURE: new_series = [original] + future else: new_series = past + [original] + future # Release the Lock on the selected Event remove_lock_information(updates) # Get the timezone from the original Event (as the series was created with that timezone in mind) timezone = original['dates']['tz'] # First find the hour and minute of the start date in local time start_time = utc_to_local(timezone, updates['dates']['start']).time() # Next convert that to seconds since midnight (which gives us a timedelta instance) delta_since_midnight = datetime.combine(date.min, start_time) - datetime.min # And calculate the new duration of the events duration = updates['dates']['end'] - updates['dates']['start'] for event in new_series: if not event.get(config.ID_FIELD): continue new_updates = {'dates': deepcopy(event['dates'])} \ if event.get(config.ID_FIELD) != original.get(config.ID_FIELD) else updates # Calculate midnight in local time for this occurrence start_of_day_local = utc_to_local(timezone, event['dates']['start'])\ .replace(hour=0, minute=0, second=0) # Then convert midnight in local time to UTC start_date_time = local_to_utc(timezone, start_of_day_local) # Finally add the delta since midnight start_date_time += delta_since_midnight # Set the new start and end times new_updates['dates']['start'] = start_date_time new_updates['dates']['end'] = start_date_time + duration # Set '_planning_schedule' on the Event item self.set_planning_schedule(new_updates) if event.get(config.ID_FIELD) != original.get(config.ID_FIELD): new_updates['skip_on_update'] = True self.patch(event[config.ID_FIELD], new_updates) app.on_updated_events_update_time( new_updates, {'_id': event[config.ID_FIELD]})
def add_to_send_list(self, alert_monitoring, profile, now, one_hour_ago, two_hours_ago, four_hours_ago, yesterday, last_week): last_run_time = parse_date_str( profile['last_run_time']) if profile.get('last_run_time') else None if last_run_time: last_run_time = utc_to_local(app.config['DEFAULT_TIMEZONE'], last_run_time) # Convert time to current date for range comparision if profile['schedule'].get('time'): hour_min = profile['schedule']['time'].split(':') schedule_today_plus_five_mins = utc_to_local( app.config['DEFAULT_TIMEZONE'], utcnow()) schedule_today_plus_five_mins = schedule_today_plus_five_mins.replace( hour=int(hour_min[0]), minute=int(hour_min[1])) schedule_today_plus_five_mins = schedule_today_plus_five_mins + datetime.timedelta( minutes=5) # Check if the time window is according to schedule if (profile['schedule']['interval'] == 'daily' and self.is_within_five_minutes( schedule_today_plus_five_mins, now) and self.is_past_range(last_run_time, yesterday)): alert_monitoring['daily']['w_lists'].append(profile) return # Check if the time window is according to schedule # Check if 'day' is according to schedule if (profile['schedule']['interval'] == 'weekly' and self.is_within_five_minutes( schedule_today_plus_five_mins, now) and schedule_today_plus_five_mins.strftime('%a').lower() == profile['schedule']['day'] and self.is_past_range(last_run_time, last_week)): alert_monitoring['weekly']['w_lists'].append(profile) return else: # Check if current time is within 'hourly' window if now.minute > 4: return if profile['schedule'][ 'interval'] == 'one_hour' and self.is_past_range( last_run_time, one_hour_ago): alert_monitoring['one']['w_lists'].append(profile) return if profile['schedule']['interval'] == 'two_hour' and now.hour % 2 == 0 and \ self.is_past_range(last_run_time, two_hours_ago): alert_monitoring['two']['w_lists'].append(profile) return if profile['schedule']['interval'] == 'four_hour' and now.hour % 4 == 0 and \ self.is_past_range(last_run_time, four_hours_ago): alert_monitoring['four']['w_lists'].append(profile) return
def test_utc_to_local(self): # with day light saving on utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC) local_dt = utc_to_local('Australia/Sydney', utc_dt) self.assertEqual(utc_dt.hour - local_dt.hour, 13) # without day light saving utc_dt = datetime(2015, 6, 8, 23, 0, 0, 0, pytz.UTC) local_dt = utc_to_local('Australia/Sydney', utc_dt) self.assertEqual(utc_dt.hour - local_dt.hour, 14)
def test_utc_to_local(self): # with day light saving on utc_dt = datetime(2015, 2, 8, 23, 0, 0, 0, pytz.UTC) local_dt = utc_to_local('Australia/Sydney', utc_dt) self.assertEqual(utc_dt.hour - local_dt.hour, 13) # without day light saving utc_dt = datetime(2015, 6, 8, 23, 0, 0, 0, pytz.UTC) local_dt = utc_to_local('Australia/Sydney', utc_dt) self.assertEqual(utc_dt.hour - local_dt.hour, 14)
def set_item_details(items): for item in items: # Prioritise the Event's slugline/name before Planning item's item["title"] = item.get("slugline") or \ item.get("name") or \ '' # Prioritise the Event's description before Planning item's event = item.get("event") or item item["description"] = event.get("definition_long") or \ event.get("definition_short") or \ item.get("description_text") or \ '' if item["type"] == "planning": # Use the Event dates if available # otherwise fall back to the Planning date if (item.get("event") or {}).get("dates"): item["dates"] = item["event"]["dates"] # Attach Events location/links for sorting/displaying item["location"] = item["event"].get("location") item["links"] = item["event"].get("links") else: item["dates"] = { "start": item["planning_date"], "tz": app.config["DEFAULT_TIMEZONE"] } if len(item.get("location") or []): # Set the items Location details if available try: item["country"] = item["location"][0]["address"][ "country"].lower() item["locality"] = item["location"][0]["address"][ "locality"].lower() except (IndexError, KeyError): pass # Construct the date string here so we don't have to use # {% if %} {% else %} statements in the template tz = item["dates"]["tz"] start_local = utc_to_local(tz, item["dates"]["start"]) start_local_str = start_local.strftime('%I:%M %P') end_local = utc_to_local( tz, item["dates"]["end"]) if item["dates"].get("end") else None end_local_str = end_local.strftime('%I:%M %P') if end_local else None tz_name = start_local.tzname() if end_local: item[ "local_date"] = f'{start_local_str} - {end_local_str} ({tz_name})' else: item["local_date"] = f'{start_local_str} ({tz_name})'
def _is_same_news_cycle(a, b): return True # not sure if we will need this cycle thing so keeping it for now CYCLE_TZ = "America/New_York" CYCLE_START_HOUR = 2 edt_time_a = utc_to_local(CYCLE_TZ, a["firstcreated"]) edt_time_b = utc_to_local(CYCLE_TZ, b["firstcreated"]) min_dt = min(edt_time_a, edt_time_b) max_dt = max(edt_time_a, edt_time_b) if min_dt.hour < CYCLE_START_HOUR and max_dt.hour >= CYCLE_START_HOUR: return False return min_dt.date() == max_dt.date()
def should_export(self, schedule, now_local): last_sent = None if schedule.get('_last_sent'): last_sent = utc_to_local(app.config['DEFAULT_TIMEZONE'], schedule['_last_sent']).replace( minute=0, second=0, microsecond=0) schedule_hour = schedule.get('hour', -1) schedule_day = schedule.get('day', -1) schedule_week_days = schedule.get('week_days') or [] # Is this export to be run today (Day of the month)? # -1 = every day if schedule_day > -1 and schedule_day != now_local.day: return False # Is this export to be run on this week day (i.e. Monday, Wednesday etc)? # None or [] = every week day week_day = now_local.strftime('%A') if len(schedule_week_days) > 0 and week_day not in schedule_week_days: return False # Is this export to be run on this hour (i.e. 8am) # -1 = every hour if schedule_hour > -1 and schedule_hour != now_local.hour: return False # This export has not been run on this hour if last_sent is not None and now_local <= last_sent: return False return True
def get_monitoring_file(self, date_items_dict, monitoring_profile=None): _file = tempfile.NamedTemporaryFile() if not date_items_dict: return _file current_date = utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()).strftime('%d/%m/%Y') doc = Document() section = Section() ss = doc.StyleSheet p1 = Paragraph(ss.ParagraphStyles.Heading1) p1.append('{} Monitoring: {} ({})'.format(app.config.get('MONITORING_REPORT_NAME', 'Newsroom'), monitoring_profile['name'], current_date)) section.append(p1) for d in date_items_dict.keys(): date_p = Paragraph(ss.ParagraphStyles.Normal) date_p.append(LINE, d.strftime('%d/%m/%Y')) section.append(date_p) for item in date_items_dict[d]: self.format_item(item, ss, monitoring_profile, section) doc.Sections.append(section) app.customize_rtf_file(doc) doc.write(_file.name) return _file
def _gen_summary_header(first_item, total_stories, new_stories): return '''<p>SUMMARY FOR {} Total number of stories transmitted - {} Total New Stories - {}</p>'''.format( utc_to_local(app.config['DEFAULT_TIMEZONE'], first_item.get('versioncreated')).strftime('%d%b%y'), total_stories, new_stories['count']).replace('\n', '<br>')
def get_local_end_of_day(context, day=None, timezone=None): tz = pytz.timezone(timezone or context.app.config['DEFAULT_TIMEZONE']) day = day or utc_to_local(tz.zone, utcnow()) return tz.localize( datetime.combine(day, time(23, 59, 59)), is_dst=None ).astimezone(pytz.utc)
def run(self, immediate=False): self.log_msg = 'Monitoring Scheduled Alerts: {}'.format(utcnow()) logger.info('{} Starting to send alerts.'.format(self.log_msg)) lock_name = get_lock_id( 'newsroom', 'monitoring_{0}'.format( 'scheduled' if not immediate else 'immediate')) if not lock(lock_name, expire=610): logger.error('{} Job already running'.format(self.log_msg)) return try: now_local = utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()) app.config['SERVER_NAME'] = urlparse( app.config['CLIENT_URL']).netloc or None celery.conf['SERVER_NAME'] = app.config['SERVER_NAME'] now_to_minute = now_local.replace(second=0, microsecond=0) if immediate: self.immediate_worker(now_to_minute) else: self.scheduled_worker(now_to_minute) except Exception as e: logger.exception(e) unlock(lock_name) remove_locks() logger.info('{} Completed sending Monitoring Scheduled Alerts.'.format( self.log_msg))
def _get_dc_images_by_field(self, archive, value): url = app.config.get('DC_URL') if not self.dc_headers: try: self._dc_login() except Exception as ex: logger.exception(ex) return None here_now = utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()) start = (here_now - timedelta(days=2)).strftime('%Y%m%d') retries = 0 while retries < 3: try: response = requests.get( url + '/archives/{}?search_docs[query]=' '({}%3D{})%26(MODDATE%3E{})' '&search_docs[format]=full'.format( archive, app.config['DC_SEARCH_FIELD'], value, start), headers={'cookie': self.dc_headers.get('Set-Cookie')}) response.raise_for_status() break except requests.exceptions.RequestException as e: logger.exception(e) try: self._dc_login() except requests.exceptions.RequestException: logger.error('DC login failed') retries += 1 if retries == 3: logger.error('Failed to get image by field from DC {}'.format(id)) return None return etree.fromstring(response.content)
def export_events_to_text(self, items, format='utf-8', template=None, tz_offset=None): for item in items: item['formatted_state'] = item['state'] if item.get('state') in [WORKFLOW_STATE.CANCELLED, WORKFLOW_STATE.RESCHEDULED, WORKFLOW_STATE.POSTPONED] else None location = item['location'][0] if len(item.get('location') or []) > 0 else None if location: format_address(location) item['formatted_location'] = location.get('name') if not location.get('formatted_address') else \ '{0}, {1}'.format(location.get('name'), location['formatted_address']) item['contacts'] = [] for contact in get_contacts_from_item(item): contact_info = ['{0} {1}'.format(contact.get('first_name'), contact.get('last_name'))] phone = None if (contact.get('job_title')): contact_info[0] = contact_info[0] + ' ({})'.format(contact['job_title']) if (len(contact.get('contact_email') or [])) > 0: contact_info.append(contact['contact_email'][0]) if (len(contact.get('contact_phone') or [])) > 0: phone = next((p for p in contact['contact_phone'] if p.get('public')), None) elif len(contact.get('mobile') or []) > 0: phone = next((m for m in contact['mobile'] if m.get('public')), None) if phone: contact_info.append(phone.get('number')) item['contacts'].append(", ".join(contact_info)) date_time_format = "%a %d %b %Y, %H:%M" item['dates']['start'] = utc_to_local(config.DEFAULT_TIMEZONE, item['dates']['start']) item['dates']['end'] = utc_to_local(config.DEFAULT_TIMEZONE, item['dates']['end']) item['schedule'] = "{0}-{1}" .format(item['dates']['start'].strftime(date_time_format), item['dates']['end'].strftime("%H:%M")) if ((item['dates']['end'] - item['dates']['start']).total_seconds() / 60) >= (24 * 60): item['schedule'] = "{0} to {1}".format(item['dates']['start'].strftime(date_time_format), item['dates']['end'].strftime(date_time_format)) if tz_offset: tz_browser = tz.tzoffset('', int(tz_offset)) item['browser_start'] = (item['dates']['start']).astimezone(tz_browser) item['browser_end'] = (item['dates']['end']).astimezone(tz_browser) set_item_place(item) return str.encode(render_template(template, items=items), format)
def test_callback(self): item = { '_id': 'foo', 'guid': 'foo', 'type': 'text', 'state': CONTENT_STATE.PUBLISHED, 'task': {}, 'profile': self.profiles[1], 'versioncreated': self.now - timedelta(minutes=5), 'headline': 'foo BELGANIGHT bar (test)', 'body_html': ''.join([ '<p>foo</p>', '<p>Disclaimer:</p>', '<p>bar</p>', ]), } with self.assertRaises(StopDuplication): macro.callback(item) # test metadata self.assertEqual(self.profiles[0], item['profile']) self.assertEqual(2, item['urgency']) self.assertIn( { 'name': 'BELGA/AG', 'qcode': 'BELGA/AG', 'scheme': 'credits', }, item['subject']) # test published published = self.app.data.find_one('published', req=None, original_id=item['_id']) self.assertIsNotNone(published) self.assertEqual(CONTENT_STATE.SCHEDULED, published['state']) # test schedule schedule = published[SCHEDULE_SETTINGS]['utc_publish_schedule'] self.assertGreaterEqual(self.now + timedelta(minutes=31), schedule) self.assertLessEqual(self.now + timedelta(minutes=29), schedule) self.assertEqual('Europe/Brussels', published[SCHEDULE_SETTINGS]['time_zone']) self.assertEqual( utc_to_local('Europe/Brussels', self.now + timedelta(minutes=30)).replace(tzinfo=utc), published['publish_schedule']) # test content self.assertEqual('foo bar', published['headline']) self.assertEqual('<p>foo</p>', published['body_html'])
def _set_revision_history(self, article): """Get revision history of published article :param dict article: """ query = { 'query': { 'filtered': { 'filter': { 'bool': { 'must': { 'term': { 'item_id': article.get('item_id') } } } } } }, 'sort': [{ 'versioncreated': { 'order': 'asc' } }] } req = ParsedRequest() repos = 'published,archived' req.args = { 'source': json.dumps(query), 'repo': repos, 'aggregations': 0 } revisions = list( get_resource_service('search').get(req=req, lookup=None)) revisions_tag = [] for rev in revisions: local_date = utc_to_local( config.DEFAULT_TIMEZONE, rev.get('firstpublished') if rev.get(ITEM_STATE) == CONTENT_STATE.PUBLISHED else rev.get('versioncreated')) date_string = datetime.strftime(local_date, '%b XXX, %Y %H:%M %Z').replace( 'XXX', str(local_date.day)) if rev.get(ITEM_STATE) == CONTENT_STATE.PUBLISHED: revisions_tag.append('<li>{} {}</li>'.format( 'First published', date_string)) else: revision_markup = '{} {}'.format('Revision published', date_string) ednote = get_text(rev.get('ednote') or '', content='html').strip() if rev.get(ITEM_STATE) == CONTENT_STATE.CORRECTED and ednote: revision_markup += '<br><i>{}</i>'.format(ednote) revisions_tag.append('<li>{}</li>'.format(revision_markup)) article['_revision_history'] = '<ul>{}</ul>'.format( ''.join(revisions_tag)) if revisions_tag else ''
def _duplicate_planning(self, original): new_plan = deepcopy(original) if new_plan.get('event_item') and new_plan.get( ITEM_STATE) == WORKFLOW_STATE.CANCELLED: # if the event is cancelled remove the link to the associated event event = get_resource_service('events').find_one( req=None, _id=new_plan.get('event_item')) if event and event.get(ITEM_STATE) == WORKFLOW_STATE.CANCELLED: del new_plan['event_item'] if (new_plan.get('expired') and new_plan.get('event_item')) or \ new_plan.get(ITEM_STATE) == WORKFLOW_STATE.RESCHEDULED: # If the Planning item has expired and is associated with an Event # then we remove the link to the associated Event as the Event would have # been expired also. # If associated event is rescheduled then remove the associated event del new_plan['event_item'] for f in ('_id', 'guid', 'lock_user', 'lock_time', 'original_creator', '_planning_schedule' 'lock_session', 'lock_action', '_created', '_updated', '_etag', 'pubstatus', 'expired', 'featured', 'state_reason', '_updates_schedule'): new_plan.pop(f, None) new_plan[ITEM_STATE] = WORKFLOW_STATE.DRAFT new_plan['guid'] = generate_guid(type=GUID_NEWSML) planning_datetime = utc_to_local(config.DEFAULT_TIMEZONE, new_plan.get('planning_date')) local_datetime = utc_to_local(config.DEFAULT_TIMEZONE, utcnow()) if planning_datetime.date() < local_datetime.date(): new_plan['planning_date'] = new_plan['planning_date'] + ( local_datetime.date() - planning_datetime.date()) for cov in new_plan.get('coverages') or []: cov.pop('assigned_to', None) cov.get('planning', {}).pop('workflow_status_reason', None) cov.pop('scheduled_updates', None) cov.get('planning', {})['scheduled'] = new_plan.get('planning_date') cov['coverage_id'] = TEMP_ID_PREFIX + 'duplicate' cov['workflow_status'] = WORKFLOW_STATE.DRAFT cov['news_coverage_status'] = {'qcode': 'ncostat:int'} return new_plan
def _check_complete(self, assignments): """ Using the photos API Look for any images that have a configured field that match the id of the assignment that have been created in the last day :param assignments: :return: A list of dictionaries that contain the assignment and matching image """ complete_assignments = list() # Restrict the search for potentialy complete assignments to the last couple of days here_now = utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()) start = (here_now - timedelta(days=5)).strftime('%Y-%m-%d') end = (here_now + timedelta(days=1)).strftime('%Y-%m-%d') service = superdesk.get_resource_service('aapmm') rq = ParsedRequest() for assignment in assignments: rq.args = { 'source': json.dumps({ "query": { "filtered": { "query": { "query_string": { "query": "{}:{}".format( app.config['DC_SEARCH_FIELD'], assignment.get('_id')) } } } }, "post_filter": { 'and': [{ 'range': { 'firstcreated': { 'gte': start, 'lte': end } } }] } }) } images = service.get(req=rq, lookup=None) # there may be multiple images that make up the assignment, we only need on to mark the # assignment as complete if images.count() > 0: complete_assignments.append({ 'assignment': assignment, 'image': images[0] }) logger.warning( 'Found {} completed assignments on the photos system'.format( len(complete_assignments))) return complete_assignments
def send_email_alert(self, items, subject, m, recipients): """ Send an email alert with the details in the body of the email. If a logo image is set in the monitoring_report_logo_path settings it will be attached to the email and can be referenced in the monitoring_export.html template as <img src="CID:logo" /> :param items: :param subject: :param m: :param recipients: :return: """ from newsroom.email import send_email general_settings = get_settings_collection().find_one( GENERAL_SETTINGS_LOOKUP) data = { 'date_items_dict': get_date_items_dict(items), 'monitoring_profile': m, 'current_date': utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()).strftime('%d/%m/%Y'), 'monitoring_report_name': app.config.get('MONITORING_REPORT_NAME', 'Newsroom') } # Attach logo to email if defined logo = None if general_settings and general_settings['values'].get( 'monitoring_report_logo_path'): image_filename = general_settings['values'].get( 'monitoring_report_logo_path') if os.path.exists(image_filename): with open(image_filename, 'rb') as img: bts = base64.b64encode(img.read()) logo = [{ 'file': bts, 'file_name': 'logo{}'.format(os.path.splitext(image_filename)[1]), 'content_type': 'image/{}'.format( os.path.splitext(image_filename)[1].replace( '.', '')), 'headers': [('Content-ID', '<logo>')] }] send_email(recipients, subject, text_body=render_template('monitoring_export.txt', **data), html_body=render_template('monitoring_export.html', **data), attachments_info=logo)
def brief_internal_routing(item: dict, **kwargs): guid = item.get('guid', 'unknown') logger.info('macro started item=%s', guid) try: assert str(item['profile']) == str( _get_profile_id(TEXT_PROFILE)), 'profile is not text' assert get_word_count(item['body_html']) < 301, 'body is too long' except AssertionError as err: logger.info('macro stop on assert item=%s error=%s', guid, err) raise StopDuplication() except KeyError as err: logger.error(err) raise StopDuplication() item.setdefault('subject', []) item['urgency'] = 2 item['profile'] = _get_profile_id(BRIEF_PROFILE) item['subject'] = _get_product_subject( _get_brief_subject(item.get('subject'))) item['status'] = CONTENT_STATE.SCHEDULED item['operation'] = 'publish' _fix_headline(item) _fix_body_html(item) # schedule +30m UTC_FIELD = 'utc_{}'.format(PUBLISH_SCHEDULE) try: published_at = item[SCHEDULE_SETTINGS][UTC_FIELD] except KeyError: published_at = utcnow() item[SCHEDULE_SETTINGS] = { 'time_zone': 'Europe/Brussels', } item[PUBLISH_SCHEDULE] = utc_to_local(item[SCHEDULE_SETTINGS]['time_zone'], published_at + timedelta(minutes=30)) update_schedule_settings(item, PUBLISH_SCHEDULE, item[PUBLISH_SCHEDULE]) item[PUBLISH_SCHEDULE] = item[PUBLISH_SCHEDULE].replace(tzinfo=None) # publish try: internal_destination_auto_publish(item) except StopDuplication: logger.info('macro done item=%s', guid) except DocumentError as err: logger.error('validation error when creating brief item=%s error=%s', guid, err) except Exception as err: logger.exception(err) # avoid another item to be created raise StopDuplication()
def _format_datetime(self, datetime, rel=False, local=False): if not datetime: return DEFAULT_DATETIME datetime = to_datetime(datetime) if rel or local: datetime = utc_to_local(cp.TZ, datetime) fmt = '%Y-%m-%dT%H:%M:%S{}'.format('%z' if rel else '', ) formatted = datetime.strftime(fmt) if rel: return formatted[:-2] + ':' + formatted[ -2:] # add : to timezone offset return formatted
def set_item_dates(item, event): """Set the item's dates to be used for sorting""" if item["type"] == "planning": # Use the Event dates if available # otherwise fall back to the Planning date if event.get("dates"): item["dates"] = item["event"]["dates"] else: item["dates"] = { "start": item["planning_date"], "tz": app.config["DEFAULT_TIMEZONE"] } # Construct the date string here so we don't have to use # {% if %} {% else %} statements in the template tz = item["dates"].get("tz") or app.config["DEFAULT_TIMEZONE"] start_local = utc_to_local(tz, item["dates"]["start"]) start_local_str = start_local.strftime("%I:%M %P") end_local = utc_to_local( tz, item["dates"]["end"]) if item["dates"].get("end") else None end_local_str = end_local.strftime("%I:%M %P") if end_local else None tz_name = start_local.tzname() # If the `tz_name` doesn't include a timezone code, # then prefix with GMT if tz_name.startswith("+"): tz_name = f"GMT{tz_name}" item["local_date"] = start_local if end_local: item["local_time"] = f"{start_local_str} - {end_local_str} ({tz_name})" else: item["local_time"] = f"{start_local_str} ({tz_name})" # Set the date string used for grouping # in the format YYYY-MM-DD item["local_date_str"] = start_local.strftime("%Y-%m-%d")
def get_next_run(schedule, now_utc=None): """Get next run time based on schedule. Schedule is day of week and time. :param dict schedule: dict with `day_of_week` and `create_at` params :param now_utc :return datetime """ if not schedule.get("is_active", False): return None if now_utc is None: now_utc = utcnow() now_utc = now_utc.replace(second=0) # Derive the first cron_list entry from the create_at and day_of_week if "create_at" in schedule and "cron_list" not in schedule: time = schedule.get("create_at").split(":") cron_days = ",".join(schedule.get("day_of_week", "*")) if len( schedule.get("day_of_week")) else "*" cron_entry = "{} {} * * {}".format(time[1], time[0], cron_days) schedule["cron_list"] = [cron_entry] schedule.pop("create_at", None) # adjust current time to the schedule's timezone tz_name = schedule.get("time_zone", "UTC") if tz_name != "UTC": current_local_datetime = utc_to_local( tz_name, now_utc) # convert utc to local time cron = croniter(schedule.get("cron_list")[0], current_local_datetime) next_run = local_to_utc(tz_name, cron.get_next(datetime)) for cron_entry in schedule.get("cron_list"): next_candidate = local_to_utc( tz_name, croniter(cron_entry, current_local_datetime).get_next(datetime)) if next_candidate < next_run: next_run = next_candidate else: cron = croniter(schedule.get("cron_list")[0], now_utc) next_run = cron.get_next(datetime) for cron_entry in schedule.get("cron_list"): next_candidate = croniter(cron_entry, now_utc).get_next(datetime) if next_candidate < next_run: next_run = next_candidate return next_run
def get_next_run(schedule, now_utc=None): """Get next run time based on schedule. Schedule is day of week and time. :param dict schedule: dict with `day_of_week` and `create_at` params :param now_utc :return datetime """ if not schedule.get('is_active', False): return None if now_utc is None: now_utc = utcnow() now_utc = now_utc.replace(second=0) # Derive the first cron_list entry from the create_at and day_of_week if 'create_at' in schedule and 'cron_list' not in schedule: time = schedule.get('create_at').split(':') cron_days = ','.join(schedule.get('day_of_week', '*')) if len( schedule.get('day_of_week')) else '*' cron_entry = '{} {} * * {}'.format(time[1], time[0], cron_days) schedule['cron_list'] = [cron_entry] schedule.pop('create_at', None) # adjust current time to the schedule's timezone tz_name = schedule.get('time_zone', 'UTC') if tz_name != 'UTC': current_local_datetime = utc_to_local( tz_name, now_utc) # convert utc to local time cron = croniter(schedule.get('cron_list')[0], current_local_datetime) next_run = local_to_utc(tz_name, cron.get_next(datetime)) for cron_entry in schedule.get('cron_list'): next_candidate = local_to_utc( tz_name, croniter(cron_entry, current_local_datetime).get_next(datetime)) if next_candidate < next_run: next_run = next_candidate else: cron = croniter(schedule.get('cron_list')[0], now_utc) next_run = cron.get_next(datetime) for cron_entry in schedule.get('cron_list'): next_candidate = croniter(cron_entry, now_utc).get_next(datetime) if next_candidate < next_run: next_run = next_candidate return next_run
def test_send_weekly_alerts(client, app): now = utcnow() now = utc_to_local(app.config['DEFAULT_TIMEZONE'], now) test_login_succeeds_for_admin(client) w = app.data.find_one('monitoring', None, _id='5db11ec55f627d8aa0b545fb') assert w is not None app.data.update( 'monitoring', ObjectId('5db11ec55f627d8aa0b545fb'), { 'schedule': { 'interval': 'weekly', 'time': (now - timedelta(minutes=1)).strftime('%H:%M'), 'day': now.strftime('%a').lower(), } }, w) app.data.insert('items', [{ '_id': 'foo_yesterday', 'headline': 'product yesterday', 'products': [{ 'code': '12345' }], "versioncreated": now - timedelta(hours=22) }]) app.data.insert('items', [{ '_id': 'foo_last_hour', 'headline': 'product three hours', 'products': [{ 'code': '12345' }], "versioncreated": now - timedelta(hours=3) }]) app.data.insert('items', [{ '_id': 'foo_four_days', 'headline': 'product four days', 'products': [{ 'code': '12345' }], "versioncreated": now - timedelta(days=4) }]) with app.mail.record_messages() as outbox: MonitoringEmailAlerts().run() assert len(outbox) == 1 assert outbox[0].recipients == [ '*****@*****.**', '*****@*****.**' ] assert outbox[0].sender == 'newsroom@localhost' assert outbox[0].subject == 'Monitoring Subject' assert 'Newsroom Monitoring: W1' in outbox[0].body assert 'monitoring-export.pdf' in outbox[0].attachments[0]
def get_monitoring_file(self, date_items_dict, monitoring_profile=None): _file = tempfile.NamedTemporaryFile() if not date_items_dict: return _file current_date = utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()).strftime('%d/%m/%Y') doc = Document() section = Section() ss = doc.StyleSheet general_settings = get_settings_collection().find_one( GENERAL_SETTINGS_LOOKUP) if general_settings and general_settings['values'].get( 'monitoring_report_logo_path'): image_filename = general_settings['values'].get( 'monitoring_report_logo_path') if os.path.exists(image_filename): try: image = LogoImage(general_settings['values'].get( 'monitoring_report_logo_path')) section.append(Paragraph(image)) except Exception as e: logger.exception(e) logger.error( 'Failed to open logo image {}'.format(image_filename)) else: logger.error( 'Unable to find logo image {}'.format(image_filename)) p1 = Paragraph(ss.ParagraphStyles.Heading1) p1.append('{} Monitoring: {} ({})'.format( app.config.get('MONITORING_REPORT_NAME', 'Newsroom'), monitoring_profile['name'], current_date)) section.append(p1) for d in date_items_dict.keys(): date_p = Paragraph(ss.ParagraphStyles.Normal) date_p.append(LINE, d.strftime('%d/%m/%Y')) section.append(date_p) for item in date_items_dict[d]: self.format_item(item, ss, monitoring_profile, section) doc.Sections.append(section) app.customize_rtf_file(doc) doc.write(_file.name) return _file
def run(self, now=None): if now: now_utc = now if isinstance(now, datetime) else local_to_utc( app.config['DEFAULT_TIMEZONE'], datetime.strptime(now, '%Y-%m-%dT%H')) else: now_utc = utcnow() now_local = utc_to_local(app.config['DEFAULT_TIMEZONE'], now_utc) logger.info('Starting to send scheduled reports: {}'.format(now_utc)) schedules = self.get_schedules() if len(schedules) < 1: logger.info('No enabled schedules found, not continuing') return # Set now to the beginning of the hour (in local time) now_local = now_local.replace(minute=0, second=0, microsecond=0) for scheduled_report in schedules: schedule_id = str(scheduled_report.get('_id')) try: if not self.should_send_report(scheduled_report, now_local): logger.info( 'Scheduled Report {} not scheduled to be sent'.format( schedule_id)) continue logger.info('Attempting to send Scheduled Report {}'.format( schedule_id)) self._send_report(scheduled_report) # Update the _last_sent of the schedule get_resource_service('scheduled_reports').system_update( scheduled_report.get('_id'), {'_last_sent': now_utc}, scheduled_report) except Exception as e: logger.error( 'Failed to generate report for {}. Error: {}'.format( schedule_id, str(e))) logger.exception(e) logger.info('Completed sending scheduled reports: {}'.format(now_utc))
def _set_revision_history(self, article): """Get revision history of published article :param dict article: """ query = { 'query': { 'filtered': { 'filter': { 'bool': { 'must': { 'term': {'item_id': article.get('item_id')} } } } } }, 'sort': [ {'versioncreated': {'order': 'asc'}} ] } req = ParsedRequest() repos = 'published,archived' req.args = {'source': json.dumps(query), 'repo': repos, 'aggregations': 0} revisions = list(get_resource_service('search').get(req=req, lookup=None)) revisions_tag = [] for rev in revisions: local_date = utc_to_local( config.DEFAULT_TIMEZONE, rev.get('firstpublished') if rev.get(ITEM_STATE) == CONTENT_STATE.PUBLISHED else rev.get('versioncreated') ) date_string = datetime.strftime(local_date, '%b XXX, %Y %H:%M %Z').replace('XXX', str(local_date.day)) if rev.get(ITEM_STATE) == CONTENT_STATE.PUBLISHED: revisions_tag.append('<li>{} {}</li>'.format('First published', date_string)) else: revision_markup = '{} {}'.format('Revision published', date_string) ednote = get_text(rev.get('ednote') or '', content='html').strip() if rev.get(ITEM_STATE) == CONTENT_STATE.CORRECTED and ednote: revision_markup += '<br><i>{}</i>'.format(ednote) revisions_tag.append('<li>{}</li>'.format(revision_markup)) article['_revision_history'] = '<ul>{}</ul>' .format(''.join(revisions_tag)) if revisions_tag else ''
def set_photo_coverage_href(coverage, planning_item): if app.config.get('MULTIMEDIA_WEBSITE_SEARCH_URL') and \ coverage['planning']['g2_content_type'] == 'picture' and \ coverage['workflow_status'] == 'completed': slugline = coverage.get('planning', {}).get('slugline', planning_item.get('slugline')) # converting the coverage schedule date to local time local_date = datetime.strftime( utc_to_local( app.config.get('DEFAULT_TIMEZONE'), datetime.strptime(coverage['planning']['scheduled'], '%Y-%m-%dT%H:%M:%S%z')), '%Y-%m-%dT%H:%M:%S%z') q = '{"DateRange":[{"Start":"%s"}],"DateCreatedFilter":"true"}' % local_date[: 10] return '{}"{}"?q={}'.format( app.config.get('MULTIMEDIA_WEBSITE_SEARCH_URL'), slugline, q)
def on_create(self, docs): for doc in docs: date = utc_to_local(doc.get('tz') or app.config['DEFAULT_TIMEZONE'], doc.get('date')) _id = date.strftime(ID_DATE_FORMAT) items = self.find(where={'_id': _id}) if items.count() > 0: raise SuperdeskApiError.badRequestError( message="Featured story already exists for this date.") self.validate_featured_attrribute(doc.get('items')) doc['_id'] = _id self.post_featured_planning(doc) # set the author set_original_creator(doc) # set timestamps update_dates_for(doc)
def get_next_run(schedule, now_utc=None): """Get next run time based on schedule. Schedule is day of week and time. :param dict schedule: dict with `day_of_week` and `create_at` params :param now_utc :return datetime """ if not schedule.get('is_active', False): return None if now_utc is None: now_utc = utcnow() now_utc = now_utc.replace(second=0) # Derive the first cron_list entry from the create_at and day_of_week if 'create_at' in schedule and 'cron_list' not in schedule: time = schedule.get('create_at').split(':') cron_days = ','.join(schedule.get('day_of_week', '*')) if len(schedule.get('day_of_week')) else '*' cron_entry = '{} {} * * {}'.format(time[1], time[0], cron_days) schedule['cron_list'] = [cron_entry] schedule.pop('create_at', None) # adjust current time to the schedule's timezone tz_name = schedule.get('time_zone', 'UTC') if tz_name != 'UTC': current_local_datetime = utc_to_local(tz_name, now_utc) # convert utc to local time cron = croniter(schedule.get('cron_list')[0], current_local_datetime) next_run = local_to_utc(tz_name, cron.get_next(datetime)) for cron_entry in schedule.get('cron_list'): next_candidate = local_to_utc(tz_name, croniter(cron_entry, current_local_datetime).get_next(datetime)) if next_candidate < next_run: next_run = next_candidate else: cron = croniter(schedule.get('cron_list')[0], now_utc) next_run = cron.get_next(datetime) for cron_entry in schedule.get('cron_list'): next_candidate = croniter(cron_entry, now_utc).get_next(datetime) if next_candidate < next_run: next_run = next_candidate return next_run
def should_send_report(scheduled_report, now_local): # Set now to the beginning of the hour (in local time) now_to_hour = now_local.replace(minute=0, second=0, microsecond=0) last_sent = None if scheduled_report.get('_last_sent'): last_sent = utc_to_local( app.config['DEFAULT_TIMEZONE'], scheduled_report.get('_last_sent')).replace(minute=0, second=0, microsecond=0) # Fix issue with incorrect schedule attributes being stored get_resource_service('scheduled_reports').set_schedule( scheduled_report) schedule = scheduled_report.get('schedule') or {} schedule_hour = schedule.get('hour', -1) schedule_day = schedule.get('day', -1) schedule_week_days = schedule.get('week_days') or [] # Is this report to be run today (Day of the month)? # -1 = every day if schedule_day > -1 and schedule_day != now_to_hour.day: return False # Is this report to be run on this week day (i.e. Monday, Wednesday etc)? # None or [] = every week day week_day = now_to_hour.strftime('%A') if len(schedule_week_days) > 0 and week_day not in schedule_week_days: return False # Is this report to be run on this hour (i.e. 8am) # -1 = every hour if schedule_hour > -1 and schedule_hour != now_to_hour.hour: return False # This report has not been run on this hour if last_sent is not None and now_to_hour <= last_sent: return False return True
def get_monitoring_file(self, date_items_dict, monitoring_profile): if not monitoring_profile or not date_items_dict: return data = { 'date_items_dict': date_items_dict, 'monitoring_profile': monitoring_profile, 'current_date': utc_to_local(app.config['DEFAULT_TIMEZONE'], utcnow()).strftime('%d/%m/%Y'), 'monitoring_report_name': app.config.get('MONITORING_REPORT_NAME', 'Newsroom') } exported_html = str.encode( render_template('monitoring_export.html', **data), 'utf-8') pdf_context = pisa.CreatePDF(exported_html) _file = pdf_context.dest _file.seek(0) return _file
def _get_date(self, article, field): return utc_to_local(config.DEFAULT_TIMEZONE or 'UTC', article.get(field) or utcnow())
def setUp(self): local_time = utc_to_local(self.app.config['DEFAULT_TIMEZONE'], utcnow()) self.desks = [ {'name': 'authoring', 'source': 'aap', 'desk_type': 'authoring'}, {'name': 'production', 'source': 'aap', 'desk_type': 'production'}, {'name': 'production2', 'source': 'aap', 'desk_type': 'production'} ] self.app.data.insert('desks', self.desks) self.articles = [ { '_id': '1', 'type': 'text', 'abstract': 'abstract item 1', 'slugline': 'slugline item 1', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'in_progress', 'versioncreated': local_time + timedelta(minutes=-1), 'task': { 'desk': self.desks[0].get('_id') } }, { '_id': '2', 'type': 'text', 'abstract': 'abstract "item 2"', 'slugline': 'slugline item 2', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'submitted', 'versioncreated': local_time + timedelta(minutes=-2), 'task': { 'desk': self.desks[1].get('_id'), 'last_authoring_desk': self.desks[0].get('_id') } }, { '_id': '2a', 'type': 'text', 'abstract': 'abstract item 2a', 'slugline': 'slugline item 2a', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(minutes=-5), 'task': { 'desk': self.desks[1].get('_id'), 'last_authoring_desk': self.desks[0].get('_id') } }, { '_id': '3', 'type': 'text', 'abstract': 'abstract item 3', 'slugline': 'slugline item 3', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'submitted', 'versioncreated': local_time + timedelta(days=-2), 'task': { 'desk': self.desks[1].get('_id'), 'last_authoring_desk': self.desks[0].get('_id') } }, { '_id': '4', 'type': 'text', 'abstract': 'abstract item 4', 'slugline': 'slugline item 4', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'corrected', 'versioncreated': local_time + timedelta(minutes=-1), 'task': { 'desk': self.desks[1].get('_id') } }, { '_id': '5', 'type': 'text', 'abstract': 'abstract item 5', 'slugline': 'slugline item 5', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'in_progress', 'versioncreated': local_time + timedelta(minutes=-1), 'task': { 'desk': self.desks[2].get('_id'), 'last_production_desk': self.desks[1].get('_id') } }, { '_id': '6', 'type': 'text', 'abstract': 'abstract item 6', 'slugline': 'slugline item 6', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(days=-1), 'task': { 'desk': self.desks[2].get('_id') } }, { '_id': '7', 'type': 'text', 'abstract': 'abstract item 7', 'slugline': 'slugline item 7', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(minutes=-7), 'task': { 'desk': self.desks[0].get('_id') } } ] self.published = [ { 'item_id': '2a', 'type': 'text', 'abstract': 'abstract item 2a', 'slugline': 'slugline item 2a', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(minutes=-5), 'task': { 'desk': self.desks[1].get('_id'), 'last_authoring_desk': self.desks[0].get('_id') } }, { 'item_id': '4', 'type': 'text', 'abstract': 'abstract item 4', 'slugline': 'slugline item 4', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'corrected', 'versioncreated': local_time + timedelta(minutes=-1), 'task': { 'desk': self.desks[1].get('_id') } }, { 'item_id': '4', 'type': 'text', 'abstract': 'abstract item 4', 'slugline': 'slugline item 4', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(minutes=-5), 'task': { 'desk': self.desks[1].get('_id') } }, { 'item_id': '6', 'type': 'text', 'abstract': 'abstract item 6', 'slugline': 'slugline item 6', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(days=-1), 'task': { 'desk': self.desks[2].get('_id') } }, { 'item_id': '7', 'type': 'text', 'abstract': 'abstract item 7', 'slugline': 'slugline item 7', 'dateline': { 'text': 'Sydney, 01 Jan AAP -', 'located': { 'city': 'Sydney' } }, 'state': 'published', 'versioncreated': local_time + timedelta(minutes=-7), 'task': { 'desk': self.desks[0].get('_id') } } ] self.app.data.insert('archive', self.articles) self.app.data.insert('published', self.published)
def create_query(self): if isinstance(self.desk_id, str): self.desk_id = ObjectId(self.desk_id) desk = get_resource_service('desks').find_one(req=None, _id=self.desk_id) if not desk: return {}, '' states = list(PUBLISH_STATES) states.extend([CONTENT_STATE.SUBMITTED, CONTENT_STATE.PROGRESS]) # using the server default timezone. current_local_time = utc_to_local(config.DEFAULT_TIMEZONE, utcnow()) query = { 'query': { 'filtered': { 'filter': { 'bool': { 'must': [ { 'term': {ITEM_TYPE: CONTENT_TYPE.TEXT} } ], 'must_not': [ { 'range': { 'versioncreated': { "lte": current_local_time.strftime('%Y-%m-%dT00:00:00%z') } } } ] } } } }, 'sort': [ {'versioncreated': 'desc'} ], 'size': 200 } desk_query = query['query']['filtered']['filter']['bool']['must'] if desk.get('desk_type') == DeskTypes.authoring.value: desk_filter = {'or': []} # if published from authoring desk desk_filter['or'].append({'and': [ {'term': {'task.desk': str(self.desk_id)}}, {'terms': {ITEM_STATE: list(PUBLISH_STATES)}} ]}) # if published send from authoring desk desk_filter['or'].append({'and': [ {'term': {'task.last_authoring_desk': str(self.desk_id)}}, {'terms': {ITEM_STATE: states}} ]}) desk_query.append(desk_filter) else: # filtering only published states for production for now. desk_query.append({'term': {'task.desk': str(self.desk_id)}}) desk_query.append({'terms': {ITEM_STATE: list(PUBLISH_STATES)}}) return query, ['archive', 'published']
def get_date_time_string(datetime_utc, str_format='%X %d%b%y'): return utc_to_local( app.config['DEFAULT_TIMEZONE'], datetime_utc ).strftime(str_format)
def _format_datetime(self, article_date, date_format='%Y-%m-%dT%H:%M:%S%z'): return datetime.strftime(utc_to_local(config.DEFAULT_TIMEZONE, article_date), date_format)