def test_broadcast_content_if_genre_is_other_than_broadcast(self): content = { 'genre': [{'name': 'Article', 'qcode': 'Article'}] } self.assertFalse(is_genre(content, BROADCAST_GENRE)) self.assertTrue(is_genre(content, 'Article'))
def on_broadcast_master_updated(self, item_event, item, takes_package_id=None, rewrite_id=None): """Runs when master item is updated. This event is called when the master story is corrected, published, re-written, new take/re-opened :param str item_event: Item operations :param dict item: item on which operation performed. :param str takes_package_id: takes_package_id. :param str rewrite_id: re-written story id. """ status = '' if not item or is_genre(item, BROADCAST_GENRE): return if item_event == ITEM_CREATE and takes_package_id: if RE_OPENS.lower() in str(item.get('anpa_take_key', '')).lower(): status = 'Story Re-opened' else: status = 'New Take Created' elif item_event == ITEM_CREATE and rewrite_id: status = 'Master Story Re-written' elif item_event == ITEM_PUBLISH: status = 'Master Story Published' elif item_event == ITEM_CORRECT: status = 'Master Story Corrected' broadcast_items = self.get_broadcast_items_from_master_story(item) if not broadcast_items: return processed_ids = set() for broadcast_item in broadcast_items: try: if broadcast_item.get('lock_user'): continue updates = { 'broadcast': broadcast_item.get('broadcast'), } if status: updates['broadcast']['status'] = status if not updates['broadcast']['takes_package_id'] and takes_package_id: updates['broadcast']['takes_package_id'] = takes_package_id if not updates['broadcast']['rewrite_id'] and rewrite_id: updates['broadcast']['rewrite_id'] = rewrite_id if not broadcast_item.get(config.ID_FIELD) in processed_ids: self._update_broadcast_status(broadcast_item, updates) # list of ids that are processed. processed_ids.add(broadcast_item.get(config.ID_FIELD)) except: logger.exception('Failed to update status for the broadcast item {}'. format(broadcast_item.get(config.ID_FIELD)))
def create(self, docs, **kwargs): target_id = request.view_args['target_id'] doc = docs[0] link_id = doc.get('link_id') desk_id = doc.get('desk') service = get_resource_service(ARCHIVE) target = service.find_one(req=None, _id=target_id) self._validate_link(target, target_id) link = {} if is_genre(target, BROADCAST_GENRE): raise SuperdeskApiError.badRequestError("Cannot add new take to the story with genre as broadcast.") if desk_id: link = {'task': {'desk': desk_id}} user = get_user() lookup = {'_id': desk_id, 'members.user': user['_id']} desk = get_resource_service('desks').find_one(req=None, **lookup) if not desk: raise SuperdeskApiError.forbiddenError("No privileges to create new take on requested desk.") link['task']['stage'] = desk['working_stage'] if link_id: link = service.find_one(req=None, _id=link_id) linked_item = self.packageService.link_as_next_take(target, link) doc.update(linked_item) build_custom_hateoas(CUSTOM_HATEOAS, doc) return [linked_item['_id']]
def _process_takes_package(self, original, updated, updates): # if target_for is set then we don't to digital client. targeted_for = updates.get('targeted_for', original.get('targeted_for')) if original[ITEM_TYPE] in {CONTENT_TYPE.TEXT, CONTENT_TYPE.PREFORMATTED} \ and not (targeted_for or is_genre(original, BROADCAST_GENRE)): # check if item is in a digital package last_updated = updates.get(config.LAST_UPDATED, utcnow()) package = self.takes_package_service.get_take_package(original) if not package: ''' If type of the item is text or preformatted then item need to be sent to digital subscribers, so package the item as a take. ''' package_id = self.takes_package_service.package_story_as_a_take(updated, {}, None) package = get_resource_service(ARCHIVE).find_one(req=None, _id=package_id) package_id = package[config.ID_FIELD] package_updates = self.process_takes(updates_of_take_to_be_published=updates, original_of_take_to_be_published=original, package=package) # If the original package is corrected then the next take shouldn't change it # back to 'published' preserve_state = package.get(ITEM_STATE, '') == CONTENT_STATE.CORRECTED and \ updates.get(ITEM_OPERATION, ITEM_PUBLISH) == ITEM_PUBLISH self._set_updates(package, package_updates, last_updated, preserve_state) package_updates.setdefault(ITEM_OPERATION, updates.get(ITEM_OPERATION, ITEM_PUBLISH)) self._update_archive(package, package_updates) package.update(package_updates) self.update_published_collection(published_item_id=package_id) self._import_into_legal_archive(package)
def get_broadcast_items_from_master_story(self, item, include_archived_repo=False): """Get the broadcast items from the master story. :param dict item: master story item :param include_archived_repo True if archived repo needs to be included in search, default is False :return list: returns list of broadcast items """ if is_genre(item, BROADCAST_GENRE): return [] ids = [str(item.get(config.ID_FIELD))] return list(self._get_broadcast_items(ids, include_archived_repo))
def get_broadcast_items_from_master_story(self, item): """ Get the broadcast items from the master story. :param dict item: master story item :return list: returns list of broadcast items """ if is_genre(item, BROADCAST_GENRE): return [] ids = [str(item.get(config.ID_FIELD))] if self.takesService.get_take_package_id(item): ids.append(str(self.takesService.get_take_package_id(item))) return list(self._get_broadcast_items(ids))
def _validate_subscribers(self, subscriber_ids, article): if not subscriber_ids: raise SuperdeskApiError.badRequestError(message='No subscribers selected!') query = {'$and': [{config.ID_FIELD: {'$in': list(subscriber_ids)}}, {'is_active': True}]} subscribers = list(get_resource_service('subscribers').get(req=None, lookup=query)) if len(subscribers) == 0: raise SuperdeskApiError.badRequestError(message='No active subscribers found!') if is_genre(article, BROADCAST_GENRE): digital_subscribers = list(self.digital(subscribers)) if len(digital_subscribers) > 0: raise SuperdeskApiError.badRequestError('Only wire subscribers can receive broadcast stories!') return subscribers
def _validate_link(self, target, target_id): """Validates the article to be linked. :param target: article to be linked :param target_id: id of the article to be linked :raises: SuperdeskApiError """ if not target: raise SuperdeskApiError.notFoundError(message='Cannot find the target item with id {}.'.format(target_id)) if target.get(EMBARGO): raise SuperdeskApiError.badRequestError("Takes can't be created for an Item having Embargo") if is_genre(target, BROADCAST_GENRE): raise SuperdeskApiError.badRequestError("Cannot add new take to the story with genre as broadcast.") if get_resource_service('published').is_rewritten_before(target['_id']): raise SuperdeskApiError.badRequestError(message='Article has been rewritten before !')
def remove_rewrite_refs(self, item): """ Remove the rewrite references from the broadcast item if the re-write is spiked. :param dict item: Re-written article of the original story """ if is_genre(item, BROADCAST_GENRE): return query = { 'query': { 'filtered': { 'filter': { 'and': [ {'term': {'genre.name': BROADCAST_GENRE}}, {'term': {'broadcast.rewrite_id': item.get(config.ID_FIELD)}} ] } } } } req = ParsedRequest() req.args = {'source': json.dumps(query)} broadcast_items = list(get_resource_service(SOURCE).get(req=req, lookup=None)) for broadcast_item in broadcast_items: try: updates = { 'broadcast': broadcast_item.get('broadcast', {}) } updates['broadcast']['rewrite_id'] = None if 'Re-written' in updates['broadcast']['status']: updates['broadcast']['status'] = '' self._update_broadcast_status(broadcast_item, updates) except: logger.exception('Failed to remove rewrite id for the broadcast item {}'. format(broadcast_item.get(config.ID_FIELD)))
def _is_take_item(self, item): """ Returns True if the item was a take """ return item[ITEM_TYPE] != CONTENT_TYPE.COMPOSITE and \ (not (item.get('targeted_for') or is_genre(item, BROADCAST_GENRE)))
def on_delete(self, doc): """ Overriding to validate the item being killed is actually eligible for kill. Validates the following: 1. Is item of type Text? 2. Is item a Broadcast Script? 3. Does item acts as a Master Story for any of the existing broadcasts? 4. Is item available in production or part of a normal package? 5. Is the associated Digital Story is available in production or part of normal package? 6. If item is a Take then is any take available in production or part of normal package? :param doc: represents the article in archived collection :type doc: dict :raises SuperdeskApiError.badRequestError() if any of the above validation conditions fail. """ bad_req_error = SuperdeskApiError.badRequestError id_field = doc[config.ID_FIELD] item_id = doc['item_id'] doc['item_id'] = id_field doc[config.ID_FIELD] = item_id if doc[ITEM_TYPE] != CONTENT_TYPE.TEXT: raise bad_req_error(message='Only Text articles are allowed to Kill in Archived repo') if is_genre(doc, BROADCAST_GENRE): raise bad_req_error(message="Killing of Broadcast Items isn't allowed in Archived repo") if get_resource_service('archive_broadcast').get_broadcast_items_from_master_story(doc, True): raise bad_req_error(message="Can't kill as this article acts as a Master Story for existing broadcast(s)") if get_resource_service(ARCHIVE).find_one(req=None, _id=doc[GUID_FIELD]): raise bad_req_error(message="Can't Kill as article is still available in production") if is_item_in_package(doc): raise bad_req_error(message="Can't kill as article is part of a Package") takes_package_service = TakesPackageService() takes_package_id = takes_package_service.get_take_package_id(doc) if takes_package_id: if get_resource_service(ARCHIVE).find_one(req=None, _id=takes_package_id): raise bad_req_error(message="Can't Kill as the Digital Story is still available in production") req = ParsedRequest() req.sort = '[("%s", -1)]' % config.VERSION takes_package = list(self.get(req=req, lookup={'item_id': takes_package_id})) if not takes_package: raise bad_req_error(message='Digital Story of the article not found in Archived repo') takes_package = takes_package[0] if is_item_in_package(takes_package): raise bad_req_error(message="Can't kill as Digital Story is part of a Package") for takes_ref in takes_package_service.get_package_refs(takes_package): if takes_ref[RESIDREF] != doc[GUID_FIELD]: if get_resource_service(ARCHIVE).find_one(req=None, _id=takes_ref[RESIDREF]): raise bad_req_error(message="Can't Kill as Take(s) are still available in production") take = list(self.get(req=None, lookup={'item_id': takes_ref[RESIDREF]})) if not take: raise bad_req_error(message='One of Take(s) not found in Archived repo') if is_item_in_package(take[0]): raise bad_req_error(message="Can't kill as one of Take(s) is part of a Package") doc['item_id'] = item_id doc[config.ID_FIELD] = id_field
def update(self, id, updates, original): """ Handles workflow of each Publish, Corrected and Killed. """ try: user = get_user() last_updated = updates.get(config.LAST_UPDATED, utcnow()) auto_publish = updates.pop('auto_publish', False) if original[ITEM_TYPE] == CONTENT_TYPE.COMPOSITE: self._publish_package_items(original, updates) queued_digital = False package = None if original[ITEM_TYPE] != CONTENT_TYPE.COMPOSITE: # if target_for is set the we don't to digital client. if not (updates.get('targeted_for', original.get('targeted_for')) or is_genre(original, BROADCAST_GENRE)): # check if item is in a digital package package = self.takes_package_service.get_take_package(original) if package: queued_digital = self._publish_takes_package(package, updates, original, last_updated) else: ''' If type of the item is text or preformatted then item need to be sent to digital subscribers. So, package the item as a take. ''' updated = copy(original) updated.update(updates) if original[ITEM_TYPE] in {CONTENT_TYPE.TEXT, CONTENT_TYPE.PREFORMATTED} and \ self.sending_to_digital_subscribers(updated): # create a takes package package_id = self.takes_package_service.package_story_as_a_take(updated, {}, None) updates[LINKED_IN_PACKAGES] = updated[LINKED_IN_PACKAGES] package = get_resource_service(ARCHIVE).find_one(req=None, _id=package_id) queued_digital = self._publish_takes_package(package, updates, original, last_updated) # queue only text items media_type = None updated = deepcopy(original) updated.update(updates) if package: media_type = SUBSCRIBER_TYPES.WIRE queued_wire = self.publish(doc=original, updates=updates, target_media_type=media_type) queued = queued_digital or queued_wire if not queued: logger.exception('Nothing is saved to publish queue for story: {} for action: {}'. format(original[config.ID_FIELD], self.publish_type)) self._update_archive(original=original, updates=updates, should_insert_into_versions=auto_publish) push_notification('item:publish', item=str(id), unique_name=original['unique_name'], desk=str(original.get('task', {}).get('desk', '')), user=str(user.get(config.ID_FIELD, ''))) except SuperdeskApiError as e: raise e except KeyError as e: raise SuperdeskApiError.badRequestError( message="Key is missing on article to be published: {}".format(str(e))) except Exception as e: logger.exception("Something bad happened while publishing %s".format(id)) raise SuperdeskApiError.internalError(message="Failed to publish the item: {}".format(str(e)))
def test_broadcast_content_if_genre_is_other_than_broadcast(self): content = {'genre': [{'name': 'Article', 'qcode': 'Article'}]} self.assertFalse(is_genre(content, BROADCAST_GENRE)) self.assertTrue(is_genre(content, 'Article'))
def on_broadcast_master_updated(self, item_event, item, takes_package_id=None, rewrite_id=None): """ This event is called when the master story is corrected, published, re-written, new take/re-opened :param str item_event: Item operations :param dict item: item on which operation performed. :param str takes_package_id: takes_package_id. :param str rewrite_id: re-written story id. """ status = '' if not item or is_genre(item, BROADCAST_GENRE): return if item_event == ITEM_CREATE and takes_package_id: if RE_OPENS.lower() in str(item.get('anpa_take_key', '')).lower(): status = 'Story Re-opened' else: status = 'New Take Created' elif item_event == ITEM_CREATE and rewrite_id: status = 'Master Story Re-written' elif item_event == ITEM_PUBLISH: status = 'Master Story Published' elif item_event == ITEM_CORRECT: status = 'Master Story Corrected' broadcast_items = self.get_broadcast_items_from_master_story(item) if not broadcast_items: return processed_ids = set() for broadcast_item in broadcast_items: try: if broadcast_item.get('lock_user'): continue updates = { 'broadcast': broadcast_item.get('broadcast'), } if status: updates['broadcast']['status'] = status if not updates['broadcast'][ 'takes_package_id'] and takes_package_id: updates['broadcast']['takes_package_id'] = takes_package_id if not updates['broadcast']['rewrite_id'] and rewrite_id: updates['broadcast']['rewrite_id'] = rewrite_id if not broadcast_item.get(config.ID_FIELD) in processed_ids: self._update_broadcast_status(broadcast_item, updates) # list of ids that are processed. processed_ids.add(broadcast_item.get(config.ID_FIELD)) except: logger.exception( 'Failed to update status for the broadcast item {}'.format( broadcast_item.get(config.ID_FIELD)))
def test_broadcast_content(self): content = { 'genre': [{'name': 'Broadcast Script', 'qcode': 'Broadcast Script'}] } self.assertTrue(is_genre(content, BROADCAST_GENRE))
def test_broadcast_content_if_genre_is_none(self): content = {"genre": None} self.assertFalse(is_genre(content, BROADCAST_GENRE))
def test_broadcast_content_if_genre_is_empty_list(self): content = { 'genre': [] } self.assertFalse(is_genre(content, BROADCAST_GENRE))
def test_broadcast_content_if_genre_is_other_than_broadcast(self): content = {"genre": [{"name": "Article", "qcode": "Article"}]} self.assertFalse(is_genre(content, BROADCAST_GENRE)) self.assertTrue(is_genre(content, "Article"))
def test_broadcast_content_if_genre_is_empty_list(self): content = {"genre": []} self.assertFalse(is_genre(content, BROADCAST_GENRE))
def test_broadcast_content_if_genre_is_none(self): content = { 'genre': None } self.assertFalse(is_genre(content, BROADCAST_GENRE))
def validate_delete_action(self, doc, allow_all_types=False): """Runs on delete of archive item. Overriding to validate the item being killed is actually eligible for kill. Validates the following: 1. Is item of type Text? 2. Is item a Broadcast Script? 3. Does item acts as a Master Story for any of the existing broadcasts? 4. Is item available in production or part of a normal package? 5. Is the associated Digital Story is available in production or part of normal package? 6. If item is a Take then is any take available in production or part of normal package? :param doc: represents the article in archived collection :type doc: dict :param allow_all_types: represents if different types of documents are allowed to be killed :type doc: bool :raises SuperdeskApiError.badRequestError() if any of the above validation conditions fail. """ bad_req_error = SuperdeskApiError.badRequestError id_field = doc[config.ID_FIELD] item_id = doc['item_id'] doc['item_id'] = id_field doc[config.ID_FIELD] = item_id if not allow_all_types and doc[ITEM_TYPE] != CONTENT_TYPE.TEXT: raise bad_req_error(message=_( 'Only Text articles are allowed to be Killed in Archived repo') ) if is_genre(doc, BROADCAST_GENRE): raise bad_req_error(message=_( "Killing of Broadcast Items isn't allowed in Archived repo")) if get_resource_service( 'archive_broadcast').get_broadcast_items_from_master_story( doc, True): raise bad_req_error(message=_( "Can't kill as this article acts as a Master Story for existing broadcast(s)" )) if get_resource_service(ARCHIVE).find_one(req=None, _id=doc[GUID_FIELD]): raise bad_req_error(message=_( "Can't Kill as article is still available in production")) if not allow_all_types and is_item_in_package(doc): raise bad_req_error( message=_("Can't kill as article is part of a Package")) takes_package_id = self._get_take_package_id(doc) if takes_package_id: if get_resource_service(ARCHIVE).find_one(req=None, _id=takes_package_id): raise bad_req_error(message=_( "Can't Kill as the Digital Story is still available in production" )) req = ParsedRequest() req.sort = '[("%s", -1)]' % config.VERSION takes_package = list( self.get(req=req, lookup={'item_id': takes_package_id})) if not takes_package: raise bad_req_error(message=_( 'Digital Story of the article not found in Archived repo')) takes_package = takes_package[0] if not allow_all_types and is_item_in_package(takes_package): raise bad_req_error(message=_( "Can't kill as Digital Story is part of a Package")) for takes_ref in self._get_package_refs(takes_package): if takes_ref[RESIDREF] != doc[GUID_FIELD]: if get_resource_service(ARCHIVE).find_one( req=None, _id=takes_ref[RESIDREF]): raise bad_req_error(message=_( "Can't Kill as Take(s) are still available in production" )) take = list( self.get(req=None, lookup={'item_id': takes_ref[RESIDREF]})) if not take: raise bad_req_error(message=_( 'One of Take(s) not found in Archived repo')) if not allow_all_types and is_item_in_package(take[0]): raise bad_req_error(message=_( "Can't kill as one of Take(s) is part of a Package" )) doc['item_id'] = item_id doc[config.ID_FIELD] = id_field
def _is_take_item(self, item): """Returns True if the item was a take.""" return item[ITEM_TYPE] != CONTENT_TYPE.COMPOSITE and \ (not (self.is_targeted(item) or is_genre(item, BROADCAST_GENRE)))
def update(self, id, updates, original): """ Handles workflow of each Publish, Corrected and Killed. """ try: user = get_user() last_updated = updates.get(config.LAST_UPDATED, utcnow()) auto_publish = updates.pop('auto_publish', False) if original[ITEM_TYPE] == CONTENT_TYPE.COMPOSITE: self._publish_package_items(original, updates) queued_digital = False package = None if original[ITEM_TYPE] != CONTENT_TYPE.COMPOSITE: # if target_for is set the we don't to digital client. if not (updates.get('targeted_for', original.get('targeted_for')) or is_genre(original, BROADCAST_GENRE)): # check if item is in a digital package package = self.takes_package_service.get_take_package(original) if package: queued_digital = self._publish_takes_package(package, updates, original, last_updated) else: ''' If type of the item is text or preformatted then item need to be sent to digital subscribers. So, package the item as a take. ''' updated = copy(original) updated.update(updates) if original[ITEM_TYPE] in {CONTENT_TYPE.TEXT, CONTENT_TYPE.PREFORMATTED} and \ self.sending_to_digital_subscribers(updated): # create a takes package package_id = self.takes_package_service.package_story_as_a_take(updated, {}, None) insert_into_versions(id_=package_id) updates[LINKED_IN_PACKAGES] = updated[LINKED_IN_PACKAGES] package = get_resource_service(ARCHIVE).find_one(req=None, _id=package_id) queued_digital = self._publish_takes_package(package, updates, original, last_updated) # queue only text items media_type = None updated = deepcopy(original) updated.update(updates) if package: media_type = SUBSCRIBER_TYPES.WIRE queued_wire = self.publish(doc=original, updates=updates, target_media_type=media_type) queued = queued_digital or queued_wire if not queued: logger.error('Nothing is saved to publish queue for story: {} for action: {}'. format(original[config.ID_FIELD], self.publish_type)) self._update_archive(original=original, updates=updates, should_insert_into_versions=auto_publish) push_notification('item:publish', item=str(id), unique_name=original['unique_name'], desk=str(original.get('task', {}).get('desk', '')), user=str(user.get(config.ID_FIELD, ''))) except SuperdeskApiError as e: raise e except KeyError as e: raise SuperdeskApiError.badRequestError( message="Key is missing on article to be published: {}".format(str(e))) except Exception as e: logger.exception("Something bad happened while publishing %s".format(id)) raise SuperdeskApiError.internalError(message="Failed to publish the item: {}".format(str(e)))