Exemplo n.º 1
0
class AdDAO(BaseDAO):
  """Ad data access object.

  Inherits from BaseDAO and implements ad specific logic for creating and
  updating ads.
  """

  def __init__(self, auth, profile_id, is_admin):
    """Initializes AdDAO with profile id and authentication scheme."""
    super(AdDAO, self).__init__(auth, profile_id, is_admin)

    self._id_field = FieldMap.AD_ID
    self._search_field = FieldMap.AD_NAME
    self._list_name = 'ads'

    self._creative_dao = CreativeDAO(auth, profile_id, is_admin)
    self._placement_dao = PlacementDAO(auth, profile_id, is_admin)
    self._campaign_dao = CampaignDAO(auth, profile_id, is_admin)
    self._event_tag_dao = EventTagDAO(auth, profile_id, is_admin)
    self._landing_page_dao = LandingPageDAO(auth, profile_id, is_admin)

    self._parent_filter_name = 'campaignIds'
    self._parent_dao = self._campaign_dao
    self._parent_filter_field_name = FieldMap.CAMPAIGN_ID

    self._entity = 'AD'

  def _api(self, iterate=False):
    """Returns an DCM API instance for this DAO."""
    return super(AdDAO, self)._api(iterate).ads()

  def _api_creatives(self, iterate=False):
    """Returns an DCM API instance for this DAO."""
    return super(AdDAO, self)._api(iterate).creatives()

  def _wait_creative_activation(self, creative_id, timeout=128):
    """Waits for a creative to become active.

    This function checks the if the creative is active in intervals that
    increase exponentially (exponential backoff).

    Args:
      creative_id: Creative identifier.
      timeout: Optional parameter, determines how many seconds to wait for the
        activation.

    Raises:
      Exception: In case the creative doesn't activate within the specified
      timeout

    """
    # Only wait for creative activation if it is a new creative trafficked by
    # this Bulkdozer session
    if store.get('CREATIVE', creative_id):
      creative = self._api_creatives().get(
          profileId=self.profile_id, id=creative_id).execute()
      wait = 2

      while not creative['active'] and timeout > 0:
        print('Waiting %s seconds for creative %s activation...' %
              (wait, creative_id))
        time.sleep(wait)
        timeout -= wait
        wait *= 2
        creative = self._api_creatives().get(
            profileId=self.profile_id, id=creative_id).execute()

      if not creative['active']:
        raise Exception(
            'Creative %s failed to activate within defined timeout' %
            creative['id'])

  def _wait_all_creative_activation(self, feed_item, timeout=128):
    """Waits for activation of all creatives that should be associated to the feed item that represents an ad.

    Args:
      feed_item: Feed item representing an Ad from the Bulkdozer feed.
      timeout: Optional parameter identifying how long to wait for all creatives
        to be activated in seconds.

    Raises:
      Exception: In case one or more creatives do not get activated within the
      specified timeout.

    """
    for association in feed_item['creative_assignment']:
      creative = self._creative_dao.get(association, required=True)
      self._wait_creative_activation(creative['id'], timeout)

  def _assignment_matches(self, item, assignment):
    if item.get(FieldMap.AD_ID, None) and assignment.get(FieldMap.AD_ID, None):
      return item.get(FieldMap.AD_ID,
                      None) == assignment.get(FieldMap.AD_ID, None)
    else:
      return item.get(FieldMap.AD_NAME,
                      '1') == assignment.get(FieldMap.AD_NAME, '2')

  def map_feeds(self, ad_feed, ad_creative_assignment, ad_placement_assignment,
                ad_event_tag_assignment, placement_feed,
                event_tag_profile_feed):
    """Maps subfeeds to the corresponding ad.

    The Ad is an object that has several other dependent entities, they could be
    other entities like creative assignment, or complex sub objects in the ad
    entity like the placement assignment. This function maps those feeds by ID
    and injects the child feeds into the feed item representing the ad.

    Also, the ad level is where placement event tag profiles are assigned, and
    therefore this function is also responsible to determining if the placement
    event tag profile should be used, or if the direct event tag assignment in
    the ad should be used.

    Args:
      ad_feed: Ad feed.
      ad_creative_assignment: Ad creative assignment feed.
      ad_placement_assignment: Ad placement assignment feed.
      placement_feed: Placement feed.
      event_tag_profile_feed: Event tag profile feed.
    """
    for ad in ad_feed:
      ad['creative_assignment'] = [
          association for association in ad_creative_assignment
          if self._assignment_matches(ad, association)
      ]

      ad['placement_assignment'] = [
          association for association in ad_placement_assignment
          if self._assignment_matches(ad, association)
      ]

      if ad.get(FieldMap.PLACEMENT_ID, None) or ad.get(FieldMap.PLACEMENT_NAME,
                                                       None):
        ad['placement_assignment'].append(ad)

      ad['event_tag_assignment'] = [
          association for association in ad_event_tag_assignment
          if self._assignment_matches(ad, association)
      ]

      if ad.get(FieldMap.EVENT_TAG_ID, None) or ad.get(FieldMap.EVENT_TAG_NAME,
                                                       None):
        ad['event_tag_assignment'].append(ad)

      # Identify all event tag profiles associated with the placements
      ad['placement_event_tag_profile'] = []
      for placement_assignment in ad['placement_assignment']:
        placement = self._placement_dao.get(placement_assignment, required=True)

        if placement:
          ad_placement = None
          for item in placement_feed:
            if int(placement['id']) == item.get(FieldMap.PLACEMENT_ID, None):
              ad_placement = item

          if ad_placement:
            event_tag_profile_name = ad_placement.get(
                FieldMap.EVENT_TAG_PROFILE_NAME, '')

            if event_tag_profile_name:
              ad['placement_event_tag_profile'] += [
                  event_tag_profile
                  for event_tag_profile in event_tag_profile_feed
                  if event_tag_profile.get(FieldMap.EVENT_TAG_PROFILE_NAME,
                                           None) == event_tag_profile_name
              ]

  def _setup_rotation_strategy(self, creative_rotation, feed_item):
    """Analyzes the feed and sets up rotation strategy for the ad.

    For better user experience, the creative rotaion values that come from the
    feed map directly to values in the UI, this function is responsible for
    translating that to API specific values for the creative rotation objects
    under the ad.

    Args:
      creative_rotation: Feed item representing the creative rotation setup from
        the feed.
      feed_item: Feed item representing the ad.
    """
    option = feed_item.get(FieldMap.CREATIVE_ROTATION, 'Even').upper()

    if option == 'EVEN':
      creative_rotation['type'] = 'CREATIVE_ROTATION_TYPE_RANDOM'
      creative_rotation['weightCalculationStrategy'] = 'WEIGHT_STRATEGY_EQUAL'
    elif option == 'SEQUENTIAL':
      creative_rotation['type'] = 'CREATIVE_ROTATION_TYPE_SEQUENTIAL'
      creative_rotation['weightCalculationStrategy'] = None
    elif option == 'CUSTOM':
      creative_rotation['type'] = 'CREATIVE_ROTATION_TYPE_RANDOM'
      creative_rotation['weightCalculationStrategy'] = 'WEIGHT_STRATEGY_CUSTOM'
    elif option == 'CLICK-THROUGH RATE':
      creative_rotation['type'] = 'CREATIVE_ROTATION_TYPE_RANDOM'
      creative_rotation[
          'weightCalculationStrategy'] = 'WEIGHT_STRATEGY_HIGHEST_CTR'
    elif option == 'OPTIMIZED':
      creative_rotation['type'] = 'CREATIVE_ROTATION_TYPE_RANDOM'
      creative_rotation[
          'weightCalculationStrategy'] = 'WEIGHT_STRATEGY_OPTIMIZED'

  def _process_update(self, item, feed_item):
    """Updates an ad based on the values from the feed.

    Args:
      item: Object representing the ad to be updated, this object is updated
        directly.
      feed_item: Feed item representing ad values from the Bulkdozer feed.
    """
    campaign = self._campaign_dao.get(feed_item, required=True)

    item['active'] = feed_item.get(FieldMap.AD_ACTIVE, True)

    if item['active']:
      self._wait_all_creative_activation(feed_item)

    self._setup_rotation_strategy(item['creativeRotation'], feed_item)

    if feed_item['creative_assignment']:
      item['creativeRotation']['creativeAssignments'] = []

    item['placementAssignments'] = []
    item['eventTagOverrides'] = []

    self._process_assignments(
        feed_item, item['creativeRotation'].get('creativeAssignments', []),
        item['placementAssignments'], item['eventTagOverrides'], campaign)

    if 'deliverySchedule' in item:
      item['deliverySchedule']['priority'] = feed_item.get(
          FieldMap.AD_PRIORITY, None)

    if feed_item.get(FieldMap.AD_HARDCUTOFF, None) != None:
      if not 'deliverySchedule' in item:
        item['deliverySchedule'] = {}

      item['deliverySchedule']['hardCutoff'] = feed_item.get(
          FieldMap.AD_HARDCUTOFF)

    item['archived'] = feed_item.get(FieldMap.AD_ARCHIVED, False)

    if 'T' in feed_item.get(FieldMap.AD_END_DATE, None):
      item['endTime'] = feed_item.get(FieldMap.AD_END_DATE, None)
    else:
      item['endTime'] = StringExtensions.convertDateStrToDateTimeStr(
          feed_item.get(FieldMap.AD_END_DATE, None), '23:59:59')

    if 'T' in feed_item.get(FieldMap.AD_START_DATE, None):
      item['startTime'] = feed_item.get(FieldMap.AD_START_DATE, None)
    else:
      item['startTime'] = StringExtensions.convertDateStrToDateTimeStr(
          feed_item.get(FieldMap.AD_START_DATE, None))

    item['name'] = feed_item.get(FieldMap.AD_NAME, None)

    self._process_landing_page(item, feed_item)

  def _process_assignments(self, feed_item, creative_assignments,
                           placement_assignments, event_tag_assignments,
                           campaign):
    """Updates the ad by setting the values of child objects based on secondary feeds.

    Args:
      feed_item: Feed item representing the ad from the Bulkdozer feed.
      creative_assignments: Feed items representing creative assignments related
        with the current ad.
      placement_assignments: Feed items representing placement assignments
        related with the current ad.
      event_tag_assignments: Feed items representing event tag assignments
        related with the current ad.
    """
    assigned_creatives = []
    assigned_placements = []
    assigned_event_tags = []

    for assignment in feed_item['creative_assignment']:
      creative = self._creative_dao.get(assignment, required=True)
      assignment[FieldMap.CREATIVE_ID] = creative['id']

      if not creative['id'] in assigned_creatives:
        assigned_creatives.append(creative['id'])

        sequence = assignment.get(FieldMap.CREATIVE_ROTATION_SEQUENCE, None)
        weight = assignment.get(FieldMap.CREATIVE_ROTATION_WEIGHT, None)

        sequence = sequence if type(sequence) is int else None
        weight = weight if type(weight) is int else None

        if assignment.get(FieldMap.AD_CREATIVE_ROTATION_START_TIME, ''):
          startTime = (
              assignment.get(FieldMap.AD_CREATIVE_ROTATION_START_TIME,
                             '') if 'T' in assignment.get(
                                 FieldMap.AD_CREATIVE_ROTATION_START_TIME, '')
              else StringExtensions.convertDateStrToDateTimeStr(
                  feed_item.get(FieldMap.AD_CREATIVE_ROTATION_START_TIME,
                                None)))
          assignment[FieldMap.AD_CREATIVE_ROTATION_START_TIME] = startTime
        else:
          startTime = None

        if assignment.get(FieldMap.AD_CREATIVE_ROTATION_END_TIME, ''):
          endTime = (
              assignment.get(FieldMap.AD_CREATIVE_ROTATION_END_TIME, '') if
              'T' in assignment.get(FieldMap.AD_CREATIVE_ROTATION_END_TIME, '')
              else StringExtensions.convertDateStrToDateTimeStr(
                  feed_item.get(FieldMap.AD_CREATIVE_ROTATION_END_TIME, None),
                  '23:59:59'))
          assignment[FieldMap.AD_CREATIVE_ROTATION_END_TIME] = endTime
        else:
          endTime = None

        lp = None
        if assignment.get(FieldMap.AD_LANDING_PAGE_ID,
                          '') != 'CAMPAIGN_DEFAULT':
          lp = self._landing_page_dao.get(assignment, required=True)
        else:
          lp = self._landing_page_dao.get(
              {FieldMap.AD_LANDING_PAGE_ID: campaign['defaultLandingPageId']},
              required=True)

        creative_assignment = {
            'active': True,
            'sequence': sequence,
            'weight': weight,
            'creativeId': assignment.get(FieldMap.CREATIVE_ID, None),
            'startTime': startTime,
            'endTime': endTime,
            'clickThroughUrl': {
                'defaultLandingPage':
                    False if
                    (assignment.get(FieldMap.AD_LANDING_PAGE_ID, '') or
                     assignment.get(FieldMap.CUSTOM_CLICK_THROUGH_URL, '')) and
                    assignment.get(FieldMap.AD_LANDING_PAGE_ID,
                                   '') != 'CAMPAIGN_DEFAULT' else True,
                'landingPageId':
                    lp.get('id', None) if lp else None,
                'customClickThroughUrl':
                    assignment.get(FieldMap.CUSTOM_CLICK_THROUGH_URL, '')
            }
        }

        if creative.get('exitCustomEvents'):
          creative_assignment['richMediaExitOverrides'] = []

          if assignment.get(FieldMap.AD_LANDING_PAGE_ID, '') or assignment.get(
              FieldMap.CUSTOM_CLICK_THROUGH_URL, ''):
            for exit_custom_event in creative.get('exitCustomEvents', []):
              creative_assignment['richMediaExitOverrides'].append({
                  'exitId': exit_custom_event['id'],
                  'enabled': True,
                  'clickThroughUrl': {
                      'defaultLandingPage':
                          False if
                          (assignment.get(FieldMap.AD_LANDING_PAGE_ID, '') or
                           assignment.get(FieldMap.CUSTOM_CLICK_THROUGH_URL, '')
                          ) and assignment.get(FieldMap.AD_LANDING_PAGE_ID, '')
                          != 'CAMPAIGN_DEFAULT' else True,
                      'landingPageId':
                          lp.get('id', None) if lp else None,
                      'customClickThroughUrl':
                          assignment.get(FieldMap.CUSTOM_CLICK_THROUGH_URL, '')
                  }
              })

        creative_assignments.append(creative_assignment)

    for assignment in feed_item['placement_assignment']:
      placement = self._placement_dao.get(assignment, required=True)
      if placement:
        assignment[FieldMap.PLACEMENT_ID] = placement['id']

        if not placement['id'] in assigned_placements:
          assigned_placements.append(placement['id'])

          placement_assignments.append({
              'active': True,
              'placementId': assignment.get(FieldMap.PLACEMENT_ID, None),
          })

    event_tags = [{
        'assignment': item,
        'event_tag': self._event_tag_dao.get(item, required=True)
    } for item in feed_item['event_tag_assignment']]

    event_tags += [{
        'assignment': item,
        'event_tag': self._event_tag_dao.get(item, required=True)
    } for item in feed_item['placement_event_tag_profile']]

    for item in event_tags:
      assignment = item['assignment']
      event_tag = item['event_tag']

      if event_tag:
        assignment[FieldMap.EVENT_TAG_ID] = event_tag['id']

        if not event_tag['id'] in assigned_event_tags:
          assigned_event_tags.append(event_tag['id'])

          event_tag_assignments.append({
              'id': event_tag['id'],
              'enabled': assignment.get(FieldMap.EVENT_TAG_ENABLED, True)
          })

  def _process_new(self, feed_item):
    """Creates a new ad DCM object from a feed item representing an ad from the Bulkdozer feed.

    This function simply creates the object to be inserted later by the BaseDAO
    object.

    Args:
      feed_item: Feed item representing the ad from the Bulkdozer feed.

    Returns:
      An ad object ready to be inserted in DCM through the API.

    """
    if feed_item.get(FieldMap.AD_ACTIVE, None):
      self._wait_all_creative_activation(feed_item)

    campaign = self._campaign_dao.get(feed_item, required=True)

    creative_assignments = []
    placement_assignments = []
    event_tag_assignments = []
    self._process_assignments(feed_item, creative_assignments,
                              placement_assignments, event_tag_assignments,
                              campaign)

    creative_rotation = {'creativeAssignments': creative_assignments}

    self._setup_rotation_strategy(creative_rotation, feed_item)

    delivery_schedule = {
        'impressionRatio': '1',
        'priority': feed_item.get(FieldMap.AD_PRIORITY, None),
        'hardCutoff': feed_item.get(FieldMap.AD_HARDCUTOFF, None)
    }

    ad = {
        'active':
            feed_item.get(FieldMap.AD_ACTIVE, None),
        'archived':
            feed_item.get(FieldMap.AD_ARCHIVED, None),
        'campaignId':
            campaign['id'],
        'creativeRotation':
            creative_rotation,
        'deliverySchedule':
            delivery_schedule,
        'endTime':
            feed_item.get(FieldMap.AD_END_DATE, None) if 'T' in feed_item.get(
                FieldMap.AD_END_DATE, None) else
            StringExtensions.convertDateStrToDateTimeStr(
                feed_item.get(FieldMap.AD_END_DATE, None), '23:59:59'),
        'name':
            feed_item.get(FieldMap.AD_NAME, None),
        'placementAssignments':
            placement_assignments,
        'startTime':
            feed_item.get(FieldMap.AD_START_DATE, None) if 'T' in feed_item.get(
                FieldMap.AD_START_DATE, None) else
            StringExtensions.convertDateStrToDateTimeStr(
                feed_item.get(FieldMap.AD_START_DATE, None)),
        'type':
            feed_item.get(FieldMap.AD_TYPE, 'AD_SERVING_STANDARD_AD'),
        'eventTagOverrides':
            event_tag_assignments
    }

    self._process_landing_page(ad, feed_item)

    return ad

  def _process_landing_page(self, item, feed_item):
    """Configures ad landing page.

    Args:
      item: DCM ad object to update.
      feed_item: Feed item representing the ad from the Bulkdozer feed
    """
    if feed_item.get(FieldMap.AD_LANDING_PAGE_ID, ''):

      landing_page = self._landing_page_dao.get(feed_item, required=True)
      item['clickThroughUrl'] = {'landingPageId': landing_page['id']}

    if feed_item.get(FieldMap.AD_URL_SUFFIX, ''):
      item['clickThroughUrlSuffixProperties'] = {
          'overrideInheritedSuffix': True,
          'clickThroughUrlSuffix': feed_item.get(FieldMap.AD_URL_SUFFIX, '')
      }
    else:
      item['clickThroughUrlSuffixProperties'] = {
          'overrideInheritedSuffix': False
      }

  def _sub_entity_map(self, assignments, item, campaign):
    """Maps ids and names of sub entities so they can be updated in the Bulkdozer feed.

    When Bulkdozer is done processing an item, it writes back the updated names
    and ids of related objects, this method makes sure those are updated in the
    ad feed.

    Args:
      assignments: List of child feeds to map.
      item: The DCM ad object that was updated or created.
      campaign: The campaign object associated with the ad.
    """
    for assignment in assignments:
      placement = self._placement_dao.get(assignment, required=True)
      event_tag = self._event_tag_dao.get(assignment, required=True)
      creative = self._creative_dao.get(assignment, required=True)

      landing_page = None
      if assignment.get(FieldMap.AD_LANDING_PAGE_ID, '') != 'CAMPAIGN_DEFAULT':
        landing_page = self._landing_page_dao.get(assignment, required=True)

      if landing_page:
        assignment[FieldMap.AD_LANDING_PAGE_ID] = landing_page['id']

      if item:
        assignment[FieldMap.AD_ID] = item['id']
        assignment[FieldMap.AD_NAME] = item['name']

      if campaign:
        assignment[FieldMap.CAMPAIGN_ID] = campaign['id']
        assignment[FieldMap.CAMPAIGN_NAME] = campaign['name']

      if placement:
        assignment[FieldMap.PLACEMENT_ID] = placement['id']
        assignment[FieldMap.PLACEMENT_NAME] = placement['name']

      if creative:
        assignment[FieldMap.CREATIVE_ID] = creative['id']
        assignment[FieldMap.CREATIVE_NAME] = creative['name']

      if event_tag:
        assignment[FieldMap.EVENT_TAG_ID] = event_tag['id']
        assignment[FieldMap.EVENT_TAG_NAME] = event_tag['name']

  def _post_process(self, feed_item, item):
    """Maps ids and names of related entities so they can be updated in the Bulkdozer feed.

    When Bulkdozer is done processing an item, it writes back the updated names
    and ids of related objects, this method makes sure those are updated in the
    ad feed.

    Args:
      feed_item: Feed item representing the ad from the Bulkdozer feed.
      item: The DCM ad being updated or created.
    """
    campaign = self._campaign_dao.get(feed_item, required=True)
    feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
    feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

    landing_page = self._landing_page_dao.get(feed_item, required=True)

    if landing_page:
      feed_item[FieldMap.AD_LANDING_PAGE_ID] = landing_page['id']

    self._sub_entity_map(feed_item['creative_assignment'], item, campaign)
    self._sub_entity_map(feed_item['placement_assignment'], item, campaign)
    self._sub_entity_map(feed_item['event_tag_assignment'], item, campaign)
Exemplo n.º 2
0
class PlacementDAO(BaseDAO):
    """Placement data access object.

  Inherits from BaseDAO and implements placement specific logic for creating and
  updating placement.
  """

    cache = {}

    def __init__(self, config, auth, profile_id, is_admin):
        """Initializes PlacementDAO with profile id and authentication scheme."""
        super(PlacementDAO, self).__init__(config, auth, profile_id, is_admin)

        self._entity = 'PLACEMENT'

        self.campaign_dao = CampaignDAO(config, auth, profile_id, is_admin)
        self.video_format_dao = VideoFormatDAO(config, auth, profile_id,
                                               is_admin)
        self.placement_group_dao = PlacementGroupDAO(config, auth, profile_id,
                                                     is_admin)

        self._id_field = FieldMap.PLACEMENT_ID
        self._search_field = FieldMap.PLACEMENT_NAME

        self._parent_filter_name = 'campaignIds'
        self._parent_filter_field_name = FieldMap.CAMPAIGN_ID
        self._parent_dao = self.campaign_dao

        self._list_name = 'placements'

        self.cache = PlacementDAO.cache

    def _api(self, iterate=False):
        """Returns an DCM API instance for this DAO."""
        return super(PlacementDAO, self)._api(iterate).placements()

    def _api_sizes(self, iterate=False):
        """Returns an DCM API instance for this DAO."""
        return super(PlacementDAO, self)._api(iterate).sizes()

    def _process_skipability(self, feed_item, item):
        """Process skipability settings.

    Args:
      feed_item: A feed item representing a placement from the bulkdozer feed;
      item: A campaign manager placement object to be updated with the
        skipability settings defined in the feed item
    """
        if feed_item.get(FieldMap.PLACEMENT_SKIPPABLE, False):
            if not 'videoSettings' in item:
                item['videoSettings'] = {}

            item['videoSettings']['skippableSettings'] = {
                'skippable': feed_item.get(FieldMap.PLACEMENT_SKIPPABLE,
                                           False),
                'skipOffset': {},
                'progressOffset': {}
            }

            skippable_settings = item['videoSettings']['skippableSettings']

            if feed_item.get(FieldMap.PLACEMENT_SKIP_OFFSET_SECONDS, None):
                skippable_settings['skipOffset'][
                    'offsetSeconds'] = feed_item.get(
                        FieldMap.PLACEMENT_SKIP_OFFSET_SECONDS, None)

            if feed_item.get(FieldMap.PLACEMENT_SKIP_OFFSET_PERCENTAGE, None):
                skippable_settings['skipOffset'][
                    'offsetPercentage'] = feed_item.get(
                        FieldMap.PLACEMENT_SKIP_OFFSET_PERCENTAGE, None)

            if feed_item.get(FieldMap.PLACEMENT_PROGRESS_OFFSET_SECONDS, None):
                skippable_settings['progressOffset'][
                    'offsetSeconds'] = feed_item.get(
                        FieldMap.PLACEMENT_SKIP_OFFSET_SECONDS, None)

            if feed_item.get(FieldMap.PLACEMENT_PROGRESS_OFFSET_PERCENTAGE,
                             None):
                skippable_settings['progressOffset'][
                    'offsetPercentage'] = feed_item.get(
                        FieldMap.PLACEMENT_SKIP_OFFSET_PERCENTAGE, None)
        else:
            if 'skippableSettings' in item and 'videoSettings' in item:
                del item['videoSettings']['skippableSettings']

    def _process_active_view_and_verification(self, placement, feed_item):
        """Updates / creates active view and verification settings.

    This method updates the CM item by setting or creating active view and
    verification settings based on the Bulkdozer feed configurations.

    Args:
      placement: The CM placement object to be updated.
      feed_item: The Bulkdozer feed item with the configurations.

    Raises:
      Exception: In case the values for active view and verification enumeration
      is invalid.
    """

        if FieldMap.PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION in feed_item:
            if feed_item.get(FieldMap.PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION,
                             None) == 'ON':
                placement['vpaidAdapterChoice'] = 'HTML5'
                placement['videoActiveViewOptOut'] = False
            elif feed_item.get(FieldMap.PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION,
                               None) == 'OFF':
                placement['vpaidAdapterChoice'] = 'DEFAULT'
                placement['videoActiveViewOptOut'] = True
            elif feed_item[
                    FieldMap.
                    PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION] == 'LET_DCM_DECIDE' or feed_item[
                        FieldMap.PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION] == '':
                placement['vpaidAdapterChoice'] = 'DEFAULT'
                placement['videoActiveViewOptOut'] = False
            else:
                raise Exception(
                    '%s is not a valid value for the placement Active View and Verification field'
                    % feed_item.get(
                        FieldMap.PLACEMENT_ACTIVE_VIEW_AND_VERIFICATION, None))

    def _process_pricing_schedule(self, item, feed_item):
        """Updates / creates pricing schedule settings.

    This method updates the CM item with pricing schedule based on
    configurations from the Bulkdozer feed.

    Args:
      item: the CM placement object to update.
      feed_item: The Bulkdozer feed item representing the settings to define.
    """
        if 'pricing_schedule' in feed_item and feed_item['pricing_schedule']:
            if not 'pricingSchedule' in item:
                item['pricingSchedule'] = {}

            item['pricingSchedule']['pricingPeriods'] = []

            for pricing_schedule in feed_item['pricing_schedule']:
                item['pricingSchedule']['pricingPeriods'].append({
                    'endDate':
                    pricing_schedule.get(FieldMap.PLACEMENT_PERIOD_END, None),
                    'startDate':
                    pricing_schedule.get(FieldMap.PLACEMENT_PERIOD_START,
                                         None),
                    'rateOrCostNanos':
                    int(
                        float(
                            pricing_schedule.get(
                                FieldMap.PLACEMENT_PERIOD_RATE)) * 1000000000),
                    'units':
                    pricing_schedule.get(FieldMap.PLACEMENT_PERIOD_UNITS),
                })

    def _process_update(self, item, feed_item):
        """Updates an placement based on the values from the feed.

    Args:
      item: Object representing the placement to be updated, this object is
        updated directly.
      feed_item: Feed item representing placement values from the Bulkdozer
        feed.
    """

        if feed_item.get(FieldMap.CAMPAIGN_ID, '') == '':
            feed_item[FieldMap.CAMPAIGN_ID] = item['campaignId']

        campaign = self.campaign_dao.get(feed_item, required=True)
        placement_group = self.placement_group_dao.get(feed_item,
                                                       required=True)

        feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
        feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

        if placement_group:
            feed_item[FieldMap.PLACEMENT_GROUP_ID] = placement_group['id']
            feed_item[FieldMap.PLACEMENT_GROUP_NAME] = placement_group['name']
            item['placementGroupId'] = placement_group['id']
        else:
            item['placementGroupId'] = None

        self._process_skipability(feed_item, item)

        item['pricingSchedule']['startDate'] = (
            StringExtensions.convertDateTimeStrToDateStr(
                feed_item.get(FieldMap.PLACEMENT_START_DATE, None))
            if feed_item.get(FieldMap.PLACEMENT_START_DATE,
                             '') else item['pricingSchedule']['startDate'])

        item['pricingSchedule']['endDate'] = (
            StringExtensions.convertDateTimeStrToDateStr(
                feed_item.get(FieldMap.PLACEMENT_END_DATE, None))
            if feed_item.get(FieldMap.PLACEMENT_END_DATE,
                             '') else item['pricingSchedule']['endDate'])

        item['pricingSchedule']['pricingType'] = feed_item.get(
            FieldMap.PLACEMENT_PRICING_SCHEDULE_COST_STRUCTURE,
            None) if feed_item.get(
                FieldMap.PLACEMENT_PRICING_SCHEDULE_COST_STRUCTURE,
                '') else item['pricingSchedule']['pricingType']

        if feed_item.get(FieldMap.PLACEMENT_PRICING_TESTING_START, None):
            item['pricingSchedule']['testingStartDate'] = feed_item.get(
                FieldMap.PLACEMENT_PRICING_TESTING_START, None)

        item['name'] = feed_item.get(
            FieldMap.PLACEMENT_NAME, None) if feed_item.get(
                FieldMap.PLACEMENT_NAME, '') else item['name']
        item['archived'] = feed_item.get(
            FieldMap.PLACEMENT_ARCHIVED, None) if feed_item.get(
                FieldMap.PLACEMENT_ARCHIVED, '') else item['archived']
        item['adBlockingOptOut'] = feed_item.get(
            FieldMap.PLACEMENT_AD_BLOCKING, False)

        self._process_transcode(item, feed_item)
        self._process_active_view_and_verification(item, feed_item)
        self._process_pricing_schedule(item, feed_item)

        key_values = feed_item.get(FieldMap.PLACEMENT_ADDITIONAL_KEY_VALUES,
                                   None)
        if key_values == '':
            if item.get('tagSetting', {}).get('additionalKeyValues'):
                del item['tagSetting']['additionalKeyValues']
        elif key_values != None:
            if not 'tagSetting' in item:
                item['tagSetting'] = {}

            item['tagSetting']['additionalKeyValues'] = key_values

    def _process_transcode(self, item, feed_item):
        """Updates / creates transcode configuration for the placement.

    This method updates the CM placement object with transcoding configuration
    from the feed.

    Args:
      item: The CM placement object to update.
      feed_item: The Bulkdozer feed item with the transcode configurations.
    """
        if feed_item.get('transcode_config', None):
            if not 'videoSettings' in item:
                item['videoSettings'] = {}

            if not 'transcodeSettings' in item['videoSettings']:
                item['videoSettings']['transcodeSettings'] = {}

            item['videoSettings']['transcodeSettings'][
                'enabledVideoFormats'] = self.video_format_dao.translate_transcode_config(
                    feed_item['transcode_config'])

            if not item['videoSettings']['transcodeSettings'][
                    'enabledVideoFormats']:
                raise Exception(
                    'Specified transcode profile did not match any placement level transcode settings in Campaign Manager'
                )

    def get_sizes(self, width, height):
        """Retrieves a creative sizes from DCM.

    Args:
      width: width of the creative.
      height: height of the creative.
      retry_count: how many times the api call should be retried in case of an
        api related error that is not a client error.

    Returns:
      The sizes object from DCM.
    """
        # TODO (mauriciod): this could potentially be in a separate SizesDAO,
        # but since we don't use it anywhere else it is probably fine.
        # May need to do it in case it becomes necessary for other entities when
        # we implement display
        return list(
            self._api_sizes(iterate=True).list(profileId=self.profile_id,
                                               height=height,
                                               width=width).execute())

    def _process_new(self, feed_item):
        """Creates a new placement DCM object from a feed item representing an placement from the Bulkdozer feed.

    This function simply creates the object to be inserted later by the BaseDAO
    object.

    Args:
      feed_item: Feed item representing the placement from the Bulkdozer feed.

    Returns:
      An placement object ready to be inserted in DCM through the API.

    """
        campaign = self.campaign_dao.get(feed_item, required=True)
        placement_group = self.placement_group_dao.get(feed_item,
                                                       required=True)

        feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
        feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

        if placement_group:
            feed_item[FieldMap.PLACEMENT_GROUP_ID] = placement_group['id']
            feed_item[FieldMap.PLACEMENT_GROUP_NAME] = placement_group['name']

        result = {
            'name':
            feed_item.get(FieldMap.PLACEMENT_NAME, None),
            'adBlockingOptOut':
            feed_item.get(FieldMap.PLACEMENT_AD_BLOCKING, False),
            'campaignId':
            campaign['id'],
            'placementGroupId':
            placement_group['id'] if placement_group else None,
            'archived':
            feed_item.get(FieldMap.PLACEMENT_ARCHIVED, False),
            'siteId':
            feed_item.get(FieldMap.SITE_ID, None),
            'paymentSource':
            'PLACEMENT_AGENCY_PAID',
            'pricingSchedule': {
                'startDate':
                StringExtensions.convertDateTimeStrToDateStr(
                    feed_item.get(FieldMap.PLACEMENT_START_DATE, None)),
                'endDate':
                StringExtensions.convertDateTimeStrToDateStr(
                    feed_item.get(FieldMap.PLACEMENT_END_DATE, None)),
                'pricingType':
                feed_item.get(
                    FieldMap.PLACEMENT_PRICING_SCHEDULE_COST_STRUCTURE, None)
                or 'PRICING_TYPE_CPM',
                'pricingPeriods': [{
                    'startDate':
                    feed_item.get(FieldMap.PLACEMENT_START_DATE, None),
                    'endDate':
                    feed_item.get(FieldMap.PLACEMENT_END_DATE, None)
                }]
            }
        }

        self._process_skipability(feed_item, result)

        if feed_item.get(FieldMap.PLACEMENT_ADDITIONAL_KEY_VALUES, None):
            result['tagSetting'] = {
                'additionalKeyValues':
                feed_item.get(FieldMap.PLACEMENT_ADDITIONAL_KEY_VALUES, None)
            }

        if feed_item.get(FieldMap.PLACEMENT_PRICING_TESTING_START, None):
            result['pricingSchedule']['testingStartDate'] = feed_item.get(
                FieldMap.PLACEMENT_PRICING_TESTING_START, None)

        self._process_active_view_and_verification(result, feed_item)

        if feed_item.get(FieldMap.PLACEMENT_TYPE,
                         None) == 'VIDEO' or feed_item[
                             FieldMap.PLACEMENT_TYPE] == 'IN_STREAM_VIDEO':
            result['compatibility'] = 'IN_STREAM_VIDEO'
            result['size'] = {'width': '0', 'height': '0'}
            result['tagFormats'] = ['PLACEMENT_TAG_INSTREAM_VIDEO_PREFETCH']
        elif feed_item[FieldMap.PLACEMENT_TYPE] == 'IN_STREAM_AUDIO':
            result['compatibility'] = 'IN_STREAM_AUDIO'
            result['size'] = {'width': '0', 'height': '0'}
            result['tagFormats'] = ['PLACEMENT_TAG_INSTREAM_VIDEO_PREFETCH']
        else:
            result['compatibility'] = 'DISPLAY'
            width = 1
            height = 1
            raw_size = feed_item.get(FieldMap.ASSET_SIZE, '0x0')
            if (raw_size and 'x' in raw_size):
                width, height = raw_size.strip().lower().split('x')

            sizes = self.get_sizes(int(width), int(height))

            if sizes:
                result['size'] = {'id': sizes[0]['id']}
            else:
                result['size'] = {'width': int(width), 'height': int(height)}

            result['tagFormats'] = [
                'PLACEMENT_TAG_STANDARD', 'PLACEMENT_TAG_JAVASCRIPT',
                'PLACEMENT_TAG_IFRAME_JAVASCRIPT',
                'PLACEMENT_TAG_IFRAME_ILAYER',
                'PLACEMENT_TAG_INTERNAL_REDIRECT', 'PLACEMENT_TAG_TRACKING',
                'PLACEMENT_TAG_TRACKING_IFRAME',
                'PLACEMENT_TAG_TRACKING_JAVASCRIPT'
            ]

        self._process_transcode(result, feed_item)
        self._process_pricing_schedule(result, feed_item)

        return result

    def _post_process(self, feed_item, item):

        for pricing_schedule in feed_item.get('pricing_schedule', []):
            placement = self.get(pricing_schedule)

            if placement:
                feed_item[FieldMap.PLACEMENT_ID] = placement['id']

    def map_placement_transcode_configs(self, placement_feed,
                                        transcode_configs_feed,
                                        pricing_schedule_feed):
        """Maps sub feeds with the parent feed based on placement id.

    Args:
      placement_feed: Bulkdozer feed representing the placements configurations.
      trascode_configs_feed: Bulkdozer feed representing the transcode configs.
      pricing_schedule_feed: Bulkdozer feed representing the pricing schedules.
    """

        for placement in placement_feed:
            placement['pricing_schedule'] = []

            for pricing_schedule in pricing_schedule_feed:
                if placement.get(FieldMap.PLACEMENT_ID,
                                 '') == pricing_schedule.get(
                                     FieldMap.PLACEMENT_ID, None):
                    placement['pricing_schedule'].append(pricing_schedule)

            transcode_id = placement.get(FieldMap.TRANSCODE_ID, '')
            placement['transcode_config'] = []
            if transcode_id:
                for transcode_config in transcode_configs_feed:
                    if transcode_id == transcode_config.get(
                            FieldMap.TRANSCODE_ID, None):
                        placement['transcode_config'].append(transcode_config)
Exemplo n.º 3
0
class PlacementGroupDAO(BaseDAO):
  """Placement group data access object.

  Inherits from BaseDAO and implements placement group specific logic for creating
  and
  updating placement group.
  """

  def __init__(self, auth, profile_id, is_admin):
    """Initializes PlacementGroupDAO with profile id and authentication scheme."""

    super(PlacementGroupDAO, self).__init__(auth, profile_id, is_admin)

    self.campaign_dao = CampaignDAO(auth, profile_id, is_admin)

    self._id_field = FieldMap.PLACEMENT_GROUP_ID
    self._search_field = FieldMap.PLACEMENT_GROUP_NAME
    self._list_name = 'placementGroups'
    self._entity = 'PLACEMENT_GROUP'

    self._parent_filter_name = 'campaignIds'
    self._parent_filter_field_name = FieldMap.CAMPAIGN_ID
    self._parent_dao = self.campaign_dao

  def _api(self, iterate=False):
    """Returns an DCM API instance for this DAO."""
    return super(PlacementGroupDAO, self)._api(iterate).placementGroups()

  def _process_update(self, item, feed_item):
    """Updates a placement group based on the values from the feed.

    Args:
      item: Object representing the placement group to be updated, this object is
        updated directly.
      feed_item: Feed item representing placement group values from the Bulkdozer
        feed.
    """
    campaign = self.campaign_dao.get(feed_item, required=True)

    feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
    feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

    item['name'] = feed_item.get(FieldMap.PLACEMENT_GROUP_NAME, None)
    item['placementGroupType'] = feed_item.get(FieldMap.PLACEMENT_GROUP_TYPE, None)
    item['pricingSchedule']['startDate'] = feed_item.get(FieldMap.PLACEMENT_GROUP_START_DATE, None)
    item['pricingSchedule']['endDate'] = feed_item.get(FieldMap.PLACEMENT_GROUP_END_DATE, None)
    item['pricingSchedule']['pricingType'] = feed_item.get(FieldMap.PLACEMENT_GROUP_PRICING_TYPE, None)

  def _process_new(self, feed_item):
    """Creates a new placement group DCM object from a feed item representing a placement group from the Bulkdozer feed.

    This function simply creates the object to be inserted later by the BaseDAO
    object.

    Args:
      feed_item: Feed item representing the placement group from the Bulkdozer
        feed.

    Returns:
      A placement group object ready to be inserted in DCM through the API.

    """
    campaign = self.campaign_dao.get(feed_item, required=True)

    feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
    feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

    return {
        'advertiserId': feed_item.get(FieldMap.ADVERTISER_ID, None),
        'campaignId': campaign['id'] if campaign else None,
        'siteId': feed_item.get(FieldMap.SITE_ID, None),
        'name': feed_item.get(FieldMap.PLACEMENT_GROUP_NAME, None),
        'placementGroupType': feed_item.get(FieldMap.PLACEMENT_GROUP_TYPE, None),
        'pricingSchedule': {
          'startDate': feed_item.get(FieldMap.PLACEMENT_GROUP_START_DATE, None),
          'endDate': feed_item.get(FieldMap.PLACEMENT_GROUP_END_DATE, None),
          'pricingType': feed_item.get(FieldMap.PLACEMENT_GROUP_PRICING_TYPE, None)
        }
    }
class CreativeAssociationDAO(BaseDAO):
    """Creative Association data access object.

  Inherits from BaseDAO and implements creative association specific logic for
  creating and
  updating creative association.
  """
    def __init__(self, auth, profile_id, is_admin):
        """Initializes CreativeAssociationDAO with profile id and authentication scheme."""
        super(CreativeAssociationDAO, self).__init__(auth, profile_id,
                                                     is_admin)

        self._id_field = FieldMap.CAMPAIGN_CREATIVE_ASSOCIATION_ID

        self.campaign_dao = CampaignDAO(auth, profile_id, is_admin)
        self.creative_dao = CreativeDAO(auth, profile_id, is_admin)

        self._parent_filter_name = None
        self._parent_filter_field_name = None

    def _api(self, iterate=False):
        """Returns an DCM API instance for this DAO."""
        return super(CreativeAssociationDAO,
                     self)._api(iterate).campaignCreativeAssociations()

    def get(self, feed_item):
        """It is not possible to retrieve creative associations from DCM,

    and they are read-only, so the get method just returns None,
    it does this to avoid errors when this is invoked polimorfically.

    For more information on the get method, refer to BaseDAO.

    Args:
      feed_item: Feed item representing the creative association from the
        Bulkdozer feed.

    Returns:
      None.
    """
        return None

    def process(self, feed_item):
        """Processes a feed item by creating the creative association in DCM.

    Args:
      feed_item: Feed item representing the creative association from the
        Bulkdozer feed.

    Returns:
      The newly created object from DCM.
    """
        if not feed_item.get(FieldMap.CAMPAIGN_CREATIVE_ASSOCIATION_ID, None):
            campaign = self.campaign_dao.get(feed_item, required=True)
            creative = self.creative_dao.get(feed_item, required=True)

            if campaign and creative:
                if campaign:
                    feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']
                    feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']

                association = {'creativeId': creative['id']}

                result = self._api().insert(profileId=self.profile_id,
                                            campaignId=campaign['id'],
                                            body=association).execute()

                feed_item[
                    FieldMap.CAMPAIGN_CREATIVE_ASSOCIATION_ID] = '%s|%s' % (
                        campaign['id'], creative['id'])

                return result

        return None
Exemplo n.º 5
0
class EventTagDAO(BaseDAO):
    """Event Tag data access object.

  Inherits from BaseDAO and implements Event Tag specific logic for creating and
  updating Event Tags.
  """
    def __init__(self, config, auth, profile_id, is_admin):
        """Initializes EventTagDAO with profile id and authentication scheme."""
        super(EventTagDAO, self).__init__(config, auth, profile_id, is_admin)

        self._id_field = FieldMap.EVENT_TAG_ID

        # This causes the dao to search event tag by name, but
        # to do so it is required to pass campaign or advertiser id
        # self._search_field = FieldMap.EVENT_TAG_NAME
        #self._search_field = FieldMap.EVENT_TAG_NAME
        self._search_field = None

        self._list_name = 'eventTags'
        self._entity = 'EVENT_TAGS'
        self._parent_filter_name = 'advertiserId'
        self._parent_dao = None
        self._parent_filter_field_name = FieldMap.ADVERTISER_ID
        self._campaign_dao = CampaignDAO(config, auth, profile_id, is_admin)

    def _api(self, iterate=False):
        """Returns an DCM API instance for this DAO."""
        return super(EventTagDAO, self)._api(iterate).eventTags()

    def pre_fetch(self, feed):
        """Pre-fetches all required items to be update into the cache.

    This increases performance for update operations.

    Args:
      feed: List of feed items to retrieve
    """
        pass

    def _get_base_search_args(self, search_string):
        return {
            'profileId': self.profile_id,
            'searchString': search_string,
            'sortField': 'NAME'
        }

    def _process_update(self, item, feed_item):
        """Processes the update of an Event Tag

    Args:
      item: Object representing the event tag to be updated, this object is
        updated directly.
      feed_item: Feed item representing event tag values from the Bulkdozer
        feed.
    """
        item['name'] = feed_item.get(FieldMap.EVENT_TAG_NAME, None)
        item['status'] = feed_item.get(FieldMap.EVENT_TAG_STATUS, None)
        item['type'] = feed_item.get(FieldMap.EVENT_TAG_TYPE, None)
        item['url'] = feed_item.get(FieldMap.EVENT_TAG_URL, None)

    def _process_new(self, feed_item):
        """Creates a new event tag DCM object from a feed item representing a event tag from the Bulkdozer feed.

    This function simply creates the object to be inserted later by the BaseDAO
    object.

    Args:
      feed_item: Feed item representing the event tag from the Bulkdozer feed.

    Returns:
      A event tag object ready to be inserted in DCM through the API.

    """
        campaign = self._campaign_dao.get(feed_item, required=True)

        return {
            'advertiserId':
            feed_item.get(FieldMap.ADVERTISER_ID, None),
            'campaignId':
            campaign.get('id', None) if campaign else None,
            'enabledByDefault':
            feed_item.get(FieldMap.EVENT_TAG_ENABLED_BY_DEFAULT, False),
            'name':
            feed_item.get(FieldMap.EVENT_TAG_NAME, None),
            'status':
            feed_item.get(FieldMap.EVENT_TAG_STATUS, None),
            'type':
            feed_item.get(FieldMap.EVENT_TAG_TYPE, None),
            'url':
            feed_item.get(FieldMap.EVENT_TAG_URL, None)
        }

    def _post_process(self, feed_item, item):
        """Updates the feed item with ids and names of related object so those can be updated in the Bulkdozer feed.

    Args:
      feed_item: The Bulkdozer feed item.
      item: The CM newly created or updated object.
    """
        campaign = self._campaign_dao.get(feed_item, required=True)

        if campaign:
            feed_item[FieldMap.CAMPAIGN_NAME] = campaign['name']
            feed_item[FieldMap.CAMPAIGN_ID] = campaign['id']