Beispiel #1
0
class CampaignDAO(BaseDAO):
    """Campaign data access object.

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

        self.landing_page_dao = LandingPageDAO(auth, profile_id, is_admin)
        self._id_field = FieldMap.CAMPAIGN_ID
        self._search_field = FieldMap.CAMPAIGN_NAME
        self._list_name = 'campaigns'

        self._parent_filter_name = None
        self._parent_filter_field_name = None

        self._entity = 'CAMPAIGN'

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

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

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

        feed_item[FieldMap.CAMPAIGN_LANDING_PAGE_ID] = lp['id']
        feed_item[FieldMap.CAMPAIGN_LANDING_PAGE_NAME] = lp['name']

        item['startDate'] = StringExtensions.convertDateTimeStrToDateStr(
            feed_item.get(FieldMap.CAMPAIGN_START_DATE, None))
        item['endDate'] = StringExtensions.convertDateTimeStrToDateStr(
            feed_item.get(FieldMap.CAMPAIGN_END_DATE, None))
        item['name'] = feed_item.get(FieldMap.CAMPAIGN_NAME, None)
        item['defaultLandingPageId'] = lp['id']

    def _process_new(self, feed_item):
        """Creates a new campaign DCM object from a feed item representing a campaign 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 campaign from the Bulkdozer feed.

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

    """
        lp = self.landing_page_dao.get(feed_item, required=True)

        feed_item[FieldMap.CAMPAIGN_LANDING_PAGE_ID] = lp['id']
        feed_item[FieldMap.CAMPAIGN_LANDING_PAGE_NAME] = lp['name']

        return {
            'advertiserId':
            feed_item.get(FieldMap.ADVERTISER_ID, None),
            'name':
            feed_item.get(FieldMap.CAMPAIGN_NAME, None),
            'startDate':
            StringExtensions.convertDateTimeStrToDateStr(
                feed_item.get(FieldMap.CAMPAIGN_START_DATE, None)),
            'endDate':
            StringExtensions.convertDateTimeStrToDateStr(
                feed_item.get(FieldMap.CAMPAIGN_END_DATE, None)),
            'defaultLandingPageId':
            lp['id']
        }
Beispiel #2
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)
Beispiel #3
0
class CreativeDAO(BaseDAO):
    """Creative data access object.

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

        self._entity = 'CREATIVE'

        self._parent_filter_name = 'advertiserId'
        self._parent_filter_field_name = FieldMap.ADVERTISER_ID

        self._id_field = FieldMap.CREATIVE_ID
        self._search_field = FieldMap.CREATIVE_NAME
        self._list_name = 'creatives'
        self._parent_dao = None

        self.creative_asset_dao = CreativeAssetDAO(auth, profile_id, is_admin,
                                                   None)
        self.landing_page_dao = LandingPageDAO(auth, profile_id, is_admin)

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

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

    def map_creative_third_party_url_feeds(self, creative_feed,
                                           third_party_url_feed):
        """Maps third party url feed to the corresponding creative.

    Third party URL is a child object to the creative, and there is a 1 creative
    to many third party urls relationship. In Bulkdozer they are represented by
    two separate tab in the feed, and this method maps the creatives to their
    respective third party URLs based on the creative ID.

    Args:
      creative_feed: Creative feed.
      third_party_url_feed: Third party url feed.
    """
        for creative in creative_feed:
            creative['third_party_urls'] = [
                third_party_url for third_party_url in third_party_url_feed
                if self._assignment_matches(creative, third_party_url)
            ]

    def map_creative_click_tag_feeds(self, creative_feed, click_tag_feed):
        """Maps click tag feed to the corresponding creative.

    Click Tag is a child object to the creative, and there is a 1 creative
    to many click tags relationship. In Bulkdozer they are represented by
    two separate tab in the feed, and this method maps the creatives to their
    respective click tags based on the creative ID.

    Args:
      creative_feed: Creative feed.
      click_tag_feed: Click tag feed.
    """
        for creative in creative_feed:
            creative['click_tags'] = [
                click_tag for click_tag in click_tag_feed
                if self._assignment_matches(creative, click_tag)
            ]

    def map_creative_and_association_feeds(self, creative_feed,
                                           creative_association_feed):
        """Maps creative association feed to the corresponding creative.

    Creative association is a child object to the creative, and there is a 1
    creative to many creative association relationship. In Bulkdozer they are
    represented by two separate tab in the feed, and this method maps the
    creatives to their respective creative association based on the creative ID.

    Args:
      creative_feed: Creative feed.
      creative_association_feed: Creative association feed.
    """
        for creative in creative_feed:
            creative['associations'] = [
                association for association in creative_association_feed
                if self._assignment_matches(creative, association)
            ]

    def _associate_third_party_urls(self, feed_item, creative):
        """Associate third party urls with the respective creative DCM object.

    This method transforms all child feed mapped earlier into DCM formatted
    associations within the creative object so it can be pushed to the API.

    Args:
      feed_item: Feed item representing the creative.
      creative: DCM creative object being created or updated.
    """
        third_party_urls = []
        for third_party_url in feed_item.get('third_party_urls', []):
            third_party_url_type = FieldMap.THIRD_PARTY_URL_TYPE_MAP.get(
                third_party_url.get(FieldMap.THIRD_PARTY_URL_TYPE, None))
            if third_party_url_type:
                third_party_urls.append({
                    'thirdPartyUrlType':
                    third_party_url_type,
                    'url':
                    third_party_url.get(FieldMap.THIRD_PARTY_URL, None)
                })

        if third_party_urls:
            creative['thirdPartyUrls'] = third_party_urls

    def _associate_click_tags(self, feed_item, creative):
        """Associate click tags with the respective creative DCM object.

    This method transforms all child feed mapped earlier into DCM formatted
    associations within the creative object so it can be pushed to the API.

    Args:
      feed_item: Feed item representing the creative.
      creative: DCM creative object being created or updated.
    """
        click_tags = []
        for click_tag in feed_item.get('click_tags', []):
            lp = self.landing_page_dao.get(
                click_tag, column_name=FieldMap.CLICK_TAG_LANDING_PAGE_ID)

            ct = {
                'eventName': click_tag.get(FieldMap.CLICK_TAG_EVENT, None),
                'name': click_tag.get(FieldMap.CLICK_TAG_NAME, None),
                'clickThroughUrl': {}
            }

            if (click_tag.get(FieldMap.CLICK_TAG_LANDING_PAGE_ID)):
                ct['clickThroughUrl']['landingPageId'] = click_tag.get(
                    FieldMap.CLICK_TAG_LANDING_PAGE_ID) if not lp else lp['id']
            elif (click_tag.get(FieldMap.CLICK_TAG_CUSTOM_CLICK_THROUGH_URL)):
                ct['clickThroughUrl']['customClickThroughUrl'] = click_tag.get(
                    FieldMap.CLICK_TAG_CUSTOM_CLICK_THROUGH_URL)

            click_tags.append(ct)

        if click_tags:
            creative['clickTags'] = click_tags

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

    Args:
      item: Object representing the creative to be updated, this object is
        updated directly.
      feed_item: Feed item representing creative values from the Bulkdozer feed.
    """
        item['name'] = feed_item.get(FieldMap.CREATIVE_NAME, None)

        self._associate_third_party_urls(feed_item, item)

        self._associate_click_tags(feed_item, item)

    def _process_new(self, feed_item):
        """Creates a new creative DCM object from a feed item representing an creative 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 creative from the Bulkdozer feed.

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

    """
        creative = {
            'advertiserId': feed_item.get(FieldMap.ADVERTISER_ID, None),
            'name': feed_item.get(FieldMap.CREATIVE_NAME, None),
            'active': True
        }

        self._associate_third_party_urls(feed_item, creative)
        self._associate_click_tags(feed_item, creative)

        # Video Creatives
        if feed_item.get(FieldMap.CREATIVE_TYPE, None) == 'VIDEO':
            creative['type'] = 'INSTREAM_VIDEO'

            for association in feed_item.get('associations', []):
                identifier = self.creative_asset_dao.get_identifier(
                    association, self._creative_asset_feed)
                creative['creativeAssets'] = [{
                    'assetIdentifier': identifier,
                    'role': 'PARENT_VIDEO'
                }]

            del creative['active']

        #Display Creatives
        elif feed_item.get(FieldMap.CREATIVE_TYPE, None) == 'DISPLAY':
            creative['type'] = 'DISPLAY'

            # Creative Size
            if feed_item.get(FieldMap.CREATIVE_WIDTH, None) and feed_item.get(
                    FieldMap.CREATIVE_HEIGHT, None):
                creative['size'] = {
                    'kind': 'dfareporting#size',
                    'width': feed_item.get(FieldMap.CREATIVE_WIDTH, None),
                    'height': feed_item.get(FieldMap.CREATIVE_HEIGHT, None)
                }

            # Add Creative Assets
            for association in feed_item.get('associations', []):
                identifier = self.creative_asset_dao.get_identifier(
                    association, self._creative_asset_feed)
                creative['creativeAssets'] = [{
                    'assetIdentifier': identifier,
                    'role': 'PRIMARY'
                }]

                # Backup Asset
                if feed_item.get(
                        FieldMap.CREATIVE_BACKUP_ASSET_ID,
                        None) and feed_item.get(
                            FieldMap.CREATIVE_BACKUP_ASSET_ID, None) != '':
                    backup_identifier = self.creative_asset_dao.get_backup_identifier(
                        association, self._creative_asset_feed)

                    creative['backupImageReportingLabel'] = feed_item.get(
                        FieldMap.CREATIVE_BACKUP_NAME, None)

                    # Parse Features
                    backup_features = feed_item.get(
                        FieldMap.BACKUP_IMAGE_FEATURES, None)
                    if backup_features != None or backup_features != '':
                        features = backup_features.split(',')
                        creative['backupImageFeatures'] = features

                    creative['backupImageTargetWindow'] = {
                        'targetWindowOption':
                        feed_item.get(
                            FieldMap.BACKUP_IMAGE_TARGET_WINDOW_OPTION, None),
                        'customHtml':
                        feed_item.get(FieldMap.BACKUP_IMAGE_CUSTOM_HTML, None)
                    }

                    lp = self.landing_page_dao.get(
                        feed_item,
                        column_name=FieldMap.
                        BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID)
                    creative['backupImageClickThroughUrl'] = {
                        'landingPageId':
                        feed_item.get(
                            FieldMap.BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID
                        ) if not lp else lp['id']
                    }

                    # Backup Image
                    creative['creativeAssets'].append({
                        'assetIdentifier': backup_identifier,
                        'role': 'BACKUP_IMAGE'
                    })

            del creative['active']

        else:
            raise Exception(
                'Only video and display are supported at the moment!')
        # (mauriciod@): I didn't pull the display creative stuff from jeltz in here,
        # because I am splitting things up differently, and the backup image will
        # have to be uploaded in the creative_assets dao

        return creative

    def map_assets_feed(self, creative_asset_feed):
        self._creative_asset_feed = creative_asset_feed

    def _post_process(self, feed_item, new_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
    creative feed.

    Args:
      feed_item: Feed item representing the creative from the Bulkdozer feed.
      item: The DCM creative being updated or created.
    """
        # TODO loop through 3p urls and update the feed
        for third_party_url in feed_item.get('third_party_urls', []):
            third_party_url[FieldMap.CREATIVE_ID] = new_item['id']
            third_party_url[FieldMap.CREATIVE_NAME] = new_item['name']

        for association in feed_item.get('associations', []):
            association[FieldMap.CREATIVE_ID] = self.get(association)['id']
            association[FieldMap.CREATIVE_NAME] = self.get(association)['name']

            dcm_association = self.creative_asset_dao.get(association,
                                                          required=True)
            if dcm_association:
                association[FieldMap.CREATIVE_ASSET_ID] = dcm_association.get(
                    'id', None)
                association[
                    FieldMap.CREATIVE_ASSET_NAME] = dcm_association.get(
                        'name', None)
                backup_lp = self.landing_page_dao.get(
                    feed_item,
                    column_name=FieldMap.
                    BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID)
                if backup_lp:
                    association[
                        FieldMap.
                        BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID] = backup_lp[
                            'id']
                    association[
                        FieldMap.
                        BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_NAME] = backup_lp[
                            'name']
                # Backup Asset Id
                backup_asset = self.creative_asset_dao.get(
                    association, column_name=FieldMap.CREATIVE_BACKUP_ASSET_ID)

                if backup_asset:
                    association[
                        FieldMap.CREATIVE_BACKUP_ASSET_ID] = backup_asset['id']

                # association[FieldMap.BACKUP_ASSET_ID] = dcm_association.get(
                #     'id', None)

        # Update Click Tags
        for click_tag in feed_item.get('click_tags', []):
            #Update Creative info
            click_tag[FieldMap.CREATIVE_ID] = new_item['id']
            click_tag[FieldMap.CREATIVE_NAME] = new_item['name']

            # Update Landing Page
            click_tag_lp = self.landing_page_dao.get(
                click_tag, column_name=FieldMap.CLICK_TAG_LANDING_PAGE_ID)
            if click_tag_lp:
                click_tag[
                    FieldMap.CLICK_TAG_LANDING_PAGE_ID] = click_tag_lp['id']
                click_tag[FieldMap.
                          CLICK_TAG_LANDING_PAGE_NAME] = click_tag_lp['name']

        # Backup Asset Id
        backup_asset = self.creative_asset_dao.get(
            feed_item, column_name=FieldMap.CREATIVE_BACKUP_ASSET_ID)

        if backup_asset:
            feed_item[FieldMap.CREATIVE_BACKUP_ASSET_ID] = backup_asset['id']

        # Backup Landing Page Id
        backup_lp = self.landing_page_dao.get(
            feed_item,
            column_name=FieldMap.BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID)
        if backup_lp:
            feed_item[
                FieldMap.
                BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_ID] = backup_lp['id']
            feed_item[
                FieldMap.
                BACKUP_IMAGE_CLICK_THROUGH_LANDING_PAGE_NAME] = backup_lp[
                    'name']