"""Logic tier for campaigns.""" import datetime import dateutil.parser from podcast.logic import user as user_logic from podcast.models import advertisement as advertisement_model from podcast.models import campaign as campaign_model from podcast.models import episode as episode_model from podcast.models import order as order_model from podcast.models import podcast as podcast_model from podcast.models.api_campaign import ApiCampaign def _accessible_order_network_ids(orders_network_ids, user_network_ids): network_ids = list(set(orders_network_ids) & user_network_ids) return network_ids def campaigns(): """Get megaphone campaigns. Soft delete campaigns that are not in megaphone and as well as their orders. Upsert campaigns from megaphone. """ megaphone_api = ApiCampaign(is_org=True) all_mp_campaigns = megaphone_api.get_all() all_mp_campaign_mp_ids = [campaign['id'] for campaign in all_mp_campaigns] formatted_campaigns = [ { 'id': campaign['id'], 'name': campaign['title'], 'advertiser_name': campaign['advertiser']['name'] } for campaign in all_mp_campaigns ] all_campaigns = campaign_model.get_all_campaigns() all_campaign_mp_ids = [campaign['id'] for campaign in all_campaigns] deleted_campaign_mp_ids = [campaign_mp_id for campaign_mp_id in all_campaign_mp_ids if campaign_mp_id not in all_mp_campaign_mp_ids] if deleted_campaign_mp_ids: campaign_model.delete_campaign_by_megaphone_ids(deleted_campaign_mp_ids) for deleted_campaign_mp_id in deleted_campaign_mp_ids: all_orders = order_model.get_orders_by_campaign_id(deleted_campaign_mp_id) deleted_order_mp_ids = [order['id'] for order in all_orders] if deleted_order_mp_ids: order_model.delete_orders_by_megaphone_ids(deleted_order_mp_ids) campaign_model.create_or_update_campaigns(formatted_campaigns) return formatted_campaigns def get_campaigns_by_megaphone_ids(ids): """Get campaigns. Returns: list (dict): campaigns """ campaigns = campaign_model.get_campaigns_by_megaphone_ids(ids) return {'items': campaigns} def campaign_orders(campaign_id): """Get megaphone campaign orders. Soft delete the orders that are not in megaphone. Upsert the orders from megaphone. """ megaphone_api = ApiCampaign(is_org=True) all_mp_orders = _normalize_orders(megaphone_api.get_all_orders(campaign_id)) all_mp_order_mp_ids = [mp_order['id'] for mp_order in all_mp_orders] all_orders = order_model.get_orders_by_campaign_id(campaign_id) all_order_mp_ids = [order['id'] for order in all_orders] deleted_order_mp_ids = [order_mp_id for order_mp_id in all_order_mp_ids if order_mp_id not in all_mp_order_mp_ids] if deleted_order_mp_ids: order_model.delete_orders_by_megaphone_ids(deleted_order_mp_ids) order_model.create_or_update_orders(all_mp_orders, campaign_id) current_user_network_ids = set(user_logic.network_ids_for_current_user()) results = [] for order in all_mp_orders: end_at = order['end_at'] has_order_ended = end_at < datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) if has_order_ended or not order['enabled']: continue order['network_ids'] = _accessible_order_network_ids(order['network_ids'], current_user_network_ids) if len(order['network_ids']) == 0: continue results.append(order) return results def get_campaign_orders_by_megaphone_ids(ids): """Get orders. Returns: list (dict): orders """ current_user = user_logic.get_current_user() current_user_network_ids = user_logic._network_ids_for_user(current_user) current_user_podcast_ids = user_logic.specific_podcast_ids_for_user(current_user) current_user_episode_ids = episode_model.get_episode_ids_by_podcast_ids( current_user_podcast_ids) if current_user_podcast_ids else [] unfiltered_orders = order_model.get_orders_by_megaphone_ids( ids, current_user_network_ids, current_user_podcast_ids, current_user_episode_ids) orders = [] # filter out order's network ids to allow only the accessible network ids. for order in unfiltered_orders: order['network_ids'] = _accessible_order_network_ids(order['network_ids'], set(current_user_network_ids)) orders.append(order) return {'items': orders} def get_campaign_orders_by_episode_ids(ids): """Get orders by episode ids.""" user_logic.current_user_owns_episodes_or_raise(ids) episodes = episode_model.get_episodes_by_ids(ids)['items'] podcast_ids = list(set([episode['podcast_id'] for episode in episodes])) return order_model.get_orders_by_episode_ids(ids, podcast_ids) def get_advertisements_by_order_ids(ids): """Get advertisements by order ids.""" return advertisement_model.get_advertisements_by_order_ids(ids) def get_advertisements_by_megaphone_ids(ids): """Get advertisements by megaphone ids .""" return advertisement_model.get_advertisements_by_megaphone_ids(ids) def advertisements(campaign_id, order_id): """Get megaphone advertisements.""" megaphone_api = ApiCampaign(is_org=True) advertisements = megaphone_api.get_all_advertisements(campaign_id, order_id) formatted_ads = [ { 'id': advertisement['id'], 'order_id': advertisement['orderId'], 'insertion_point': advertisement['insertionPoint'], 'pos': advertisement['position'], 'audio_file': advertisement['audioFile'], 'name': advertisement.get('displayName') } for advertisement in advertisements ] advertisement_model.create_or_update_advertisements(formatted_ads, order_id) return advertisements def get_podcast_upcoming_sold_percent_by_ids(ids): """Get podcast sold percent by ids.""" podcast_ids = [int(podcast_id) for podcast_id in ids] user_logic.current_user_owns_podcast_ids_or_raise(podcast_ids) today = datetime.date.today().strftime('%Y-%m-%d') one_month = (datetime.date.today() + datetime.timedelta(days=28)).strftime('%Y-%m-%d') result = [{'id': podcast_id, 'upcoming_sold_percent': None} for podcast_id in podcast_ids] for podcast_id in podcast_ids: episodes = episode_model.get_episodes(podcast_id, start_date=today, end_date=one_month)['items'] episode_ids = [episode['id'] for episode in episodes] orders = get_campaign_orders_by_episode_ids(episode_ids)['items'] order_ids = [order['id'] for order in orders] ads = get_advertisements_by_order_ids(order_ids)['items'] total = _total_planned_for_podcast(episodes) sold = _sold_for_podcast(episodes, orders, ads) if total == 0: continue percent_sold = round((sold / total) * 100) for r in result: if r['id'] == podcast_id: r['upcoming_sold_percent'] = percent_sold return {'items': result} def _total_planned_for_podcast(episodes): total_planned = 0 for episode in episodes: total_planned += episode['planned_pre_roll_count'] \ + episode['planned_mid_roll_count'] \ + episode['planned_post_roll_count'] return total_planned def _sold_for_podcast(episodes, orders, ads): sold_spots = 0 for episode in episodes: total_planned = episode['planned_pre_roll_count'] \ + episode['planned_mid_roll_count'] \ + episode['planned_post_roll_count'] if total_planned == 0: continue episode_orders = [ order for order in orders if ((episode['id'] in order['episode_ids']) or (episode['podcast_id'] in order['podcast_ids'])) ] episode_orders = [ order for order in episode_orders if ((episode['published_date'] >= order['start_at']) and (episode['published_date'] <= order['end_at'])) ] episode_orders = [ order for order in episode_orders if order['position'] > 0 ] planned_hash = { 'pre': [0] * episode['planned_pre_roll_count'], 'mid': [0] * episode['planned_mid_roll_count'], 'post': [0] * episode['planned_post_roll_count'], } for order in episode_orders: for ad in ads: if ad['campaign_order_megaphone_id'] == order['id']: try: spot = planned_hash[ad['insertion_point']][order['position'] - 1] if spot == 0: planned_hash[ad['insertion_point']][order['position'] - 1] = 1 sold_spots += 1 except IndexError: # if the order position is greater than the planned count pass return sold_spots def _normalize_orders(orders): """Normalize fields and change megaphone targets to our networks.""" mp_podcast_to_network, mp_episode_to_network = _get_dicts_to_networks(orders) normalized_orders = [] for order in orders: network_ids = _get_network_ids_from_target(order['targets'], mp_podcast_to_network, mp_episode_to_network) normalized_orders.append({ 'id': order['id'], 'campaign_id': order['campaignId'], 'name': order['title'], 'start_at': dateutil.parser.parse(order['startAt']).astimezone(datetime.timezone.utc), 'end_at': dateutil.parser.parse(order['endAt']).astimezone(datetime.timezone.utc), 'pos': order['position'], 'priority': order['priority'], 'enabled': 1 if order['state'] == 'enabled' else 0, 'network_ids': list(network_ids), 'targets': order['targets'] }) return normalized_orders def _get_dicts_to_networks(orders): podcasts, episodes = _get_podcasts_and_episodes(orders) mp_episode_to_network = {} mp_podcast_to_network = {} podcast_to_network = {} for podcast in podcasts: mp_podcast_to_network[podcast['megaphone_id']] = podcast['network_id'] podcast_to_network[podcast['id']] = podcast['network_id'] for episode in episodes: mp_episode_to_network[episode['megaphone_id']] = podcast_to_network[episode['podcast_id']] return mp_podcast_to_network, mp_episode_to_network def _get_podcasts_and_episodes(orders): megaphone_podcast_ids = set() megaphone_episode_ids = set() for order in orders: for target in order['targets']: if target['type'] == 'episode': megaphone_episode_ids.add(target['id']) if target['type'] == 'podcast': megaphone_podcast_ids.add(target['id']) episodes = episode_model.get_episodes_by_megaphone_ids(megaphone_episode_ids)['items'] podcast_ids = [episode['podcast_id'] for episode in episodes] podcasts = podcast_model.get_podcasts_by_megaphone_ids(megaphone_podcast_ids)['items'] podcasts.extend(podcast_model.get_podcasts_by_ids(podcast_ids)['items']) return podcasts, episodes def _get_network_ids_from_target(targets, mp_podcast_to_network, mp_episode_to_network): network_ids = set() for target in targets: if target['type'] == 'episode' and target['id'] in mp_episode_to_network: network_ids.add(mp_episode_to_network[target['id']]) if target['type'] == 'podcast' and target['id'] in mp_podcast_to_network: network_ids.add(mp_podcast_to_network[target['id']]) return network_ids