def update_flight(link, campaign, triggered_by=None): """Add/update a reddit campaign as an Adzerk Flight""" if getattr(campaign, 'external_flight_id', None) is not None: az_flight = adzerk_api.Flight.get(campaign.external_flight_id) else: az_flight = None # backwards compatability if campaign.platform == "mobile": campaign.platform = "mobile_web" campaign._commit() campaign_overdelivered = is_overdelivered(campaign) delayed_start = campaign.start_date + datetime.timedelta(minutes=15) if delayed_start >= campaign.end_date: # start time must be before end time delayed_start = campaign.start_date keywords = set(campaign.target.subreddit_names) targets_frontpage = Frontpage.name in keywords if targets_frontpage: keywords.remove(Frontpage.name) # Don't allow nsfw links to show up on the frontpage if link.over_18: keywords.add("!s.frontpage") elif targets_frontpage: keywords.add("s.frontpage") campaign_needs_approval = (not campaign.is_approved and promote.campaign_needs_review(campaign, link)) campaign_is_paused = campaign.paused campaign_needs_payment = not promote.charged_or_not_needed(campaign) campaign_is_terminated = campaign.is_terminated is_active = flight_is_active( needs_approval=campaign_needs_approval, is_paused=campaign_is_paused, needs_payment=campaign_needs_payment, is_terminated=campaign_is_terminated, is_deleted=campaign._deleted, is_overdelivered=campaign_overdelivered, ) d = { 'StartDate': date_to_adzerk(delayed_start), 'EndDate': date_to_adzerk(campaign.end_date), 'OptionType': 1, # 1: CPM, 2: Remainder 'IsUnlimited': False, 'IsFullSpeed': False, 'Keywords': '\n'.join(list(keywords)), 'CampaignId': link.external_campaign_id, 'PriorityId': g.az_selfserve_priorities[campaign.priority_name], 'IsDeleted': False, 'IsActive': is_active, } if campaign.frequency_cap: d.update({'IsFreqCap': True, 'FreqCap': campaign.frequency_cap, 'FreqCapDuration': FREQUENCY_CAP_DURATION_HOURS, 'FreqCapType': FREQ_CAP_TYPE.hour}) else: d['IsFreqCap'] = None if campaign.is_house: # house campaigns are flat rate (free) 100% impression goal d.update({ 'Price': 0, # free/0 revenue 'Impressions': 100, # 100% impressions/rotates with other house campaigns. 'GoalType': 2, # percentage 'RateType': 1, # flat }) else: # price is bid if the rate type is cpm/cpc d.update({ 'Price': campaign.bid_pennies / 100., # convert cents to dollars 'GoalType': GOAL_TYPE_BY_COST_BASIS[campaign.cost_basis], # goal should be based on billing type 'RateType': RATE_TYPE_BY_COST_BASIS[campaign.cost_basis], }) stop_showing_onclick = campaign.cost_basis == promo.PROMOTE_COST_BASIS.cpc d.update({ "BehavioralTargeting": { "onClick": { "stopShowingAdsFromFlight": stop_showing_onclick, }, }, }) if campaign.cost_basis == promo.PROMOTE_COST_BASIS.fixed_cpm: d['Impressions'] = campaign.impressions + ADZERK_IMPRESSION_BUMP else: total_budget_dollars = campaign.total_budget_pennies / 100. daily_cap_dollars = total_budget_dollars / max(campaign.ndays, 1) ndays = campaign.ndays padding = 1 + (1. / (ndays + 1)) lifetime_cap = int(math.ceil(total_budget_dollars)) # budget caps must be whole dollar amounts. round things up to prevent # things from underdelivering. d.update({ 'CapType': 4, 'LifetimeCapAmount': lifetime_cap, }) if not campaign.use_daily_budget_cap: # set the daily cap to the same as the lifetime cap since # `update_changed` doesn't handle unsetting attributes. d['DailyCapAmount'] = lifetime_cap else: d['DailyCapAmount'] = int(math.ceil(daily_cap_dollars * padding)) # Zerkel queries here if campaign.mobile_os: queries_list = [] if 'iOS' in campaign.mobile_os: ios_targets = get_mobile_targeting_query(os_str='iOS', lookup_str='modelName', devices=campaign.ios_devices, versions=campaign.ios_version_range) queries_list.append(ios_targets) if 'Android' in campaign.mobile_os: android_targets = get_mobile_targeting_query(os_str='Android', lookup_str='formFactor', devices=campaign.android_devices, versions=campaign.android_version_range) queries_list.append(android_targets) if campaign.platform == 'all': queries_list.append('($device.formFactor CONTAINS "desktop")') mobile_targeting_query = ' OR '.join(queries_list) d.update({ 'CustomTargeting': mobile_targeting_query, }) else: d.update({ 'CustomTargeting': '', }) if campaign.platform != 'all': site_targeting = [{ 'SiteId': g.az_selfserve_site_ids[campaign.platform], 'IsExclude': False, 'ZoneId': None, }] if az_flight: site_targeting[0]['FlightId'] = az_flight.Id # Check equality more specifically to reduce spam in the PromotionLog. update_site_targeting = True if az_flight and az_flight.SiteZoneTargeting: # Loop through the existing site targeting and remove the `Id` param. old_site_targeting = map( lambda (index, value): { key: value[key] for key in value.keys() if key != "Id"}, enumerate(az_flight.SiteZoneTargeting), ) update_site_targeting = old_site_targeting != site_targeting if update_site_targeting: d.update({ 'SiteZoneTargeting': site_targeting, }) elif az_flight and az_flight.SiteZoneTargeting: d.update({ 'SiteZoneTargeting': [], }) # special handling for location conversions between reddit and adzerk if campaign.location: campaign_country = campaign.location.country campaign_region = campaign.location.region if campaign.location.metro: campaign_metro = int(campaign.location.metro) else: campaign_metro = None if az_flight and az_flight.GeoTargeting: # special handling for geotargeting of existing flights # can't update geotargeting through the Flight endpoint, do it manually existing = az_flight.GeoTargeting[0] az_geotarget = adzerk_api.GeoTargeting._from_item(existing) if (campaign.location and (campaign_country != az_geotarget.CountryCode or campaign_region != az_geotarget.Region or campaign_metro != az_geotarget.MetroCode or az_geotarget.IsExclude)): # existing geotargeting doesn't match current location az_geotarget.CountryCode = campaign_country az_geotarget.Region = campaign_region az_geotarget.MetroCode = campaign_metro az_geotarget.IsExclude = False az_geotarget._send(az_flight.Id) log_text = 'updated geotargeting to %s' % campaign.location PromotionLog.add(link, log_text) elif not campaign.location: # flight should no longer be geotargeted az_geotarget._delete(az_flight.Id) log_text = 'deleted geotargeting' PromotionLog.add(link, log_text) # only allow one geotarget per flight for existing in az_flight.GeoTargeting[1:]: az_geotarget = adzerk_api.GeoTargeting._from_item(existing) az_geotarget._delete(az_flight.Id) # NOTE: need to unset GeoTargeting otherwise it will be added to the # flight again when we _send updates az_flight.GeoTargeting = None elif campaign.location: # flight endpoint works when a new flight is being created or an # existing one that didn't have geotargeting is being updated d.update({ 'GeoTargeting': [{ 'CountryCode': campaign_country, 'Region': campaign_region, 'MetroCode': campaign_metro, 'IsExclude': False, }], }) else: # no geotargeting, either a new flight is being created or an existing # flight is being updated that wasn't geotargeted d.update({ 'GeoTargeting': [], }) request_error = None additional_data = dict( requires_approval=campaign_needs_approval, requires_payment=campaign_needs_payment, is_overdelivered=campaign_overdelivered, is_paused=campaign_is_paused, is_terminated=campaign_is_terminated, ) if az_flight: try: changed = update_changed(az_flight, **d) except adzerk_api.AdzerkError as e: request_error = e finally: g.ad_events.adzerk_api_request( request_type="update_flight", thing=campaign, request_body=d, triggered_by=triggered_by, additional_data=additional_data, request_error=request_error, ) # re-raise after sending event to requeue item. if request_error: raise request_error change_strs = make_change_strings(changed) if campaign_overdelivered: billable = promote.get_billable_impressions(campaign) over_str = 'overdelivered %s/%s' % (billable, campaign.impressions) change_strs.append(over_str) if change_strs: log_text = 'updated %s: ' % az_flight + ', '.join(change_strs) else: log_text = None else: d.update({'Name': campaign._fullname}) try: az_flight = adzerk_api.Flight.create(**d) except adzerk_api.AdzerkError as e: request_error = e finally: g.ad_events.adzerk_api_request( request_type="create_flight", thing=campaign, request_body=d, triggered_by=triggered_by, additional_data=additional_data, request_error=request_error, ) # re-raise after sending event to requeue item. if request_error: raise request_error campaign.external_flight_id = az_flight.Id campaign._commit() PromoCampaignByFlightIdCache.add(campaign) log_text = 'created %s' % az_flight if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) if campaign_overdelivered: campaign.external_flight_overdelivered = True campaign._commit() return az_flight
def update_flight(link, campaign, az_campaign): """Add/update a reddit campaign as an Adzerk Flight""" if getattr(campaign, 'external_flight_id', None) is not None: az_flight = adzerk_api.Flight.get(campaign.external_flight_id) else: az_flight = None # backwards compatability if campaign.platform == "mobile": campaign.platform = "mobile_web" campaign._commit() campaign_overdelivered = is_overdelivered(campaign) delayed_start = campaign.start_date + datetime.timedelta(minutes=15) if delayed_start >= campaign.end_date: # start time must be before end time delayed_start = campaign.start_date keywords = campaign.target.subreddit_names # Don't allow nsfw links to show up on the frontpage if link.over_18: keywords.append('!' + Frontpage.name) d = { 'StartDate': date_to_adzerk(delayed_start), 'EndDate': date_to_adzerk(campaign.end_date), 'OptionType': 1, # 1: CPM, 2: Remainder 'IsUnlimited': False, 'IsFullSpeed': False, 'Keywords': '\n'.join(keywords), 'CampaignId': az_campaign.Id, 'PriorityId': g.az_selfserve_priorities[campaign.priority_name], 'IsDeleted': False, 'IsActive': (not campaign.paused and promote.charged_or_not_needed(campaign) and not (campaign._deleted or campaign_overdelivered)), } if campaign.frequency_cap: d.update({'IsFreqCap': True, 'FreqCap': campaign.frequency_cap, 'FreqCapDuration': FREQUENCY_CAP_DURATION_HOURS, 'FreqCapType': FREQ_CAP_TYPE.hour}) else: d['IsFreqCap'] = None if campaign.is_house: # house campaigns are flat rate (free) 100% impression goal d.update({ 'Price': 0, # free/0 revenue 'Impressions': 100, # 100% impressions/rotates with other house campaigns. 'GoalType': 2, # percentage 'RateType': 1, # flat }) else: # price is bid if the rate type is cpm/cpc d.update({ 'Price': campaign.bid_pennies / 100., # convert cents to dollars 'GoalType': GOAL_TYPE_BY_COST_BASIS[campaign.cost_basis], # goal should be based on billing type 'RateType': RATE_TYPE_BY_COST_BASIS[campaign.cost_basis], }) if campaign.cost_basis == promo.PROMOTE_COST_BASIS.fixed_cpm: d['Impressions'] = campaign.impressions + ADZERK_IMPRESSION_BUMP else: total_budget_dollars = campaign.total_budget_pennies / 100. # budget caps must be whole dollar amounts. round things up to prevent # things from underdelivering. d.update({ 'CapType': 4, 'DailyCapAmount': int(math.ceil(total_budget_dollars / campaign.ndays)), 'LifetimeCapAmount': int(math.ceil(total_budget_dollars)), }) # Zerkel queries here if campaign.mobile_os: queries_list = [] if 'iOS' in campaign.mobile_os: ios_targets = get_mobile_targeting_query(os_str='iOS', lookup_str='modelName', devices=campaign.ios_devices, versions=campaign.ios_version_range) queries_list.append(ios_targets) if 'Android' in campaign.mobile_os: android_targets = get_mobile_targeting_query(os_str='Android', lookup_str='formFactor', devices=campaign.android_devices, versions=campaign.android_version_range) queries_list.append(android_targets) if campaign.platform == 'all': queries_list.append('($device.formFactor CONTAINS "desktop")') mobile_targeting_query = ' OR '.join(queries_list) d.update({ 'CustomTargeting': mobile_targeting_query, }) else: d.update({ 'CustomTargeting': '', }) if campaign.platform != 'all': d.update({ 'SiteZoneTargeting': [{ 'SiteId': g.az_selfserve_site_ids[campaign.platform], 'IsExclude': False, }], }) # special handling for location conversions between reddit and adzerk if campaign.location: campaign_country = campaign.location.country campaign_region = campaign.location.region if campaign.location.metro: campaign_metro = int(campaign.location.metro) else: campaign_metro = None if az_flight and az_flight.GeoTargeting: # special handling for geotargeting of existing flights # can't update geotargeting through the Flight endpoint, do it manually existing = az_flight.GeoTargeting[0] az_geotarget = adzerk_api.GeoTargeting._from_item(existing) if (campaign.location and (campaign_country != az_geotarget.CountryCode or campaign_region != az_geotarget.Region or campaign_metro != az_geotarget.MetroCode or az_geotarget.IsExclude)): # existing geotargeting doesn't match current location az_geotarget.CountryCode = campaign_country az_geotarget.Region = campaign_region az_geotarget.MetroCode = campaign_metro az_geotarget.IsExclude = False az_geotarget._send(az_flight.Id) log_text = 'updated geotargeting to %s' % campaign.location PromotionLog.add(link, log_text) elif not campaign.location: # flight should no longer be geotargeted az_geotarget._delete(az_flight.Id) log_text = 'deleted geotargeting' PromotionLog.add(link, log_text) # only allow one geotarget per flight for existing in az_flight.GeoTargeting[1:]: az_geotarget = adzerk_api.GeoTargeting._from_item(existing) az_geotarget._delete(az_flight.Id) # NOTE: need to unset GeoTargeting otherwise it will be added to the # flight again when we _send updates az_flight.GeoTargeting = None elif campaign.location: # flight endpoint works when a new flight is being created or an # existing one that didn't have geotargeting is being updated d.update({ 'GeoTargeting': [{ 'CountryCode': campaign_country, 'Region': campaign_region, 'MetroCode': campaign_metro, 'IsExclude': False, }], }) else: # no geotargeting, either a new flight is being created or an existing # flight is being updated that wasn't geotargeted d.update({ 'GeoTargeting': [], }) if az_flight: changed = update_changed(az_flight, **d) change_strs = make_change_strings(changed) if campaign_overdelivered: billable = promote.get_billable_impressions(campaign) over_str = 'overdelivered %s/%s' % (billable, campaign.impressions) change_strs.append(over_str) if change_strs: log_text = 'updated %s: ' % az_flight + ', '.join(change_strs) else: log_text = None else: d.update({'Name': campaign._fullname}) az_flight = adzerk_api.Flight.create(**d) campaign.external_flight_id = az_flight.Id campaign._commit() PromoCampaignByFlightIdCache.add(campaign) log_text = 'created %s' % az_flight if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) if campaign_overdelivered: campaign.external_flight_overdelivered = True campaign._commit() return az_flight
def update_flight(link, campaign, az_campaign): """Add/update a reddit campaign as an Adzerk Flight""" if getattr(campaign, 'external_flight_id', None) is not None: az_flight = adzerk_api.Flight.get(campaign.external_flight_id) else: az_flight = None campaign_overdelivered = is_overdelivered(campaign) delayed_start = campaign.start_date + datetime.timedelta(minutes=15) if delayed_start >= campaign.end_date: # start time must be before end time delayed_start = campaign.start_date d = { 'StartDate': date_to_adzerk(delayed_start), 'EndDate': date_to_adzerk(campaign.end_date), 'OptionType': 1, # 1: CPM, 2: Remainder 'IsUnlimited': False, 'IsFullSpeed': False, 'Keywords': '\n'.join(campaign.target.subreddit_names), 'CampaignId': az_campaign.Id, 'PriorityId': g.az_selfserve_priorities[campaign.priority_name], 'IsDeleted': False, 'IsActive': (promote.charged_or_not_needed(campaign) and not (campaign._deleted or campaign_overdelivered)), } if campaign.frequency_cap and campaign.frequency_cap_duration: d.update({'IsFreqCap': True, 'FreqCap': campaign.frequency_cap, 'FreqCapDuration': campaign.frequency_cap_duration, 'FreqCapType': FREQ_CAP_TYPE.hour}) else: d['IsFreqCap'] = None is_cpm = hasattr(campaign, 'cpm') and campaign.priority.cpm if is_cpm: d.update({ 'Price': campaign.cpm / 100., # convert from cents to dollars 'Impressions': campaign.impressions + ADZERK_IMPRESSION_BUMP, 'GoalType': 1, # 1: Impressions 'RateType': 2, # 2: CPM }) else: d.update({ 'Price': campaign.bid, 'Impressions': 100, 'GoalType': 2, # 2: Percentage 'RateType': 1, # 1: Flat }) # Zerkel queries here if campaign.mobile_os: queries_list = [] if 'iOS' in campaign.mobile_os: ios_targets = get_mobile_targeting_query(os_str='iOS', lookup_str='modelName', devices=campaign.ios_devices, versions=campaign.ios_version_range) queries_list.append(ios_targets) if 'Android' in campaign.mobile_os: android_targets = get_mobile_targeting_query(os_str='Android', lookup_str='formFactor', devices=campaign.android_devices, versions=campaign.android_version_range) queries_list.append(android_targets) if campaign.platform == 'all': queries_list.append('($device.formFactor CONTAINS "desktop")') mobile_targeting_query = ' OR '.join(queries_list) d.update({ 'CustomTargeting': mobile_targeting_query, }) else: d.update({ 'CustomTargeting': '', }) if campaign.platform != 'all': siteZones = [] if campaign.platform == 'desktop': siteZones.append({ 'SiteId': g.az_selfserve_site_id, 'IsExclude': False, }) elif campaign.platform == 'mobile': siteZones.append({ 'SiteId': g.az_selfserve_mobile_web_site_id, 'IsExclude': False, }) if len(siteZones): d.update({ 'SiteZoneTargeting': siteZones }) # special handling for location conversions between reddit and adzerk if campaign.location: campaign_country = campaign.location.country campaign_region = campaign.location.region if campaign.location.metro: campaign_metro = int(campaign.location.metro) else: campaign_metro = None if az_flight and az_flight.GeoTargeting: # special handling for geotargeting of existing flights # can't update geotargeting through the Flight endpoint, do it manually existing = az_flight.GeoTargeting[0] az_geotarget = adzerk_api.GeoTargeting._from_item(existing) if (campaign.location and (campaign_country != az_geotarget.CountryCode or campaign_region != az_geotarget.Region or campaign_metro != az_geotarget.MetroCode or az_geotarget.IsExclude)): # existing geotargeting doesn't match current location az_geotarget.CountryCode = campaign_country az_geotarget.Region = campaign_region az_geotarget.MetroCode = campaign_metro az_geotarget.IsExclude = False az_geotarget._send(az_flight.Id) log_text = 'updated geotargeting to %s' % campaign.location PromotionLog.add(link, log_text) elif not campaign.location: # flight should no longer be geotargeted az_geotarget._delete(az_flight.Id) log_text = 'deleted geotargeting' PromotionLog.add(link, log_text) # only allow one geotarget per flight for existing in az_flight.GeoTargeting[1:]: az_geotarget = adzerk_api.GeoTargeting._from_item(existing) az_geotarget._delete(az_flight.Id) # NOTE: need to unset GeoTargeting otherwise it will be added to the # flight again when we _send updates az_flight.GeoTargeting = None elif campaign.location: # flight endpoint works when a new flight is being created or an # existing one that didn't have geotargeting is being updated d.update({ 'GeoTargeting': [{ 'CountryCode': campaign_country, 'Region': campaign_region, 'MetroCode': campaign_metro, 'IsExclude': False, }], }) else: # no geotargeting, either a new flight is being created or an existing # flight is being updated that wasn't geotargeted d.update({ 'GeoTargeting': [], }) if az_flight: changed = update_changed(az_flight, **d) change_strs = make_change_strings(changed) if campaign_overdelivered: billable = promote.get_billable_impressions(campaign) over_str = 'overdelivered %s/%s' % (billable, campaign.impressions) change_strs.append(over_str) if change_strs: log_text = 'updated %s: ' % az_flight + ', '.join(change_strs) else: log_text = None else: d.update({'Name': campaign._fullname}) az_flight = adzerk_api.Flight.create(**d) campaign.external_flight_id = az_flight.Id campaign._commit() PromoCampaignByFlightIdCache.add(campaign) log_text = 'created %s' % az_flight if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) if campaign_overdelivered: campaign.external_flight_overdelivered = True campaign._commit() return az_flight
def update_flight(link, campaign, az_campaign): """Add/update a reddit campaign as an Adzerk Flight""" if getattr(campaign, 'external_flight_id', None) is not None: az_flight = adzerk_api.Flight.get(campaign.external_flight_id) else: az_flight = None # backwards compatability if campaign.platform == "mobile": campaign.platform = "mobile_web" campaign._commit() campaign_overdelivered = is_overdelivered(campaign) delayed_start = campaign.start_date + datetime.timedelta(minutes=15) if delayed_start >= campaign.end_date: # start time must be before end time delayed_start = campaign.start_date keywords = campaign.target.subreddit_names # Don't allow nsfw links to show up on the frontpage if link.over_18: keywords.append('!' + Frontpage.name) d = { 'StartDate': date_to_adzerk(delayed_start), 'EndDate': date_to_adzerk(campaign.end_date), 'OptionType': 1, # 1: CPM, 2: Remainder 'IsUnlimited': False, 'IsFullSpeed': False, 'Keywords': '\n'.join(keywords), 'CampaignId': az_campaign.Id, 'PriorityId': g.az_selfserve_priorities[campaign.priority_name], 'IsDeleted': False, 'IsActive': (not campaign.paused and promote.charged_or_not_needed(campaign) and not (campaign._deleted or campaign_overdelivered)), } if campaign.frequency_cap: d.update({ 'IsFreqCap': True, 'FreqCap': campaign.frequency_cap, 'FreqCapDuration': FREQUENCY_CAP_DURATION_HOURS, 'FreqCapType': FREQ_CAP_TYPE.hour }) else: d['IsFreqCap'] = None if campaign.is_house: # house campaigns are flat rate (free) 100% impression goal d.update({ 'Price': 0, # free/0 revenue 'Impressions': 100, # 100% impressions/rotates with other house campaigns. 'GoalType': 2, # percentage 'RateType': 1, # flat }) else: # price is bid if the rate type is cpm/cpc d.update({ 'Price': campaign.bid_pennies / 100., # convert cents to dollars 'GoalType': GOAL_TYPE_BY_COST_BASIS[ campaign.cost_basis], # goal should be based on billing type 'RateType': RATE_TYPE_BY_COST_BASIS[campaign.cost_basis], }) if campaign.cost_basis == promo.PROMOTE_COST_BASIS.fixed_cpm: d['Impressions'] = campaign.impressions + ADZERK_IMPRESSION_BUMP else: total_budget_dollars = campaign.total_budget_pennies / 100. # budget caps must be whole dollar amounts. round things up to prevent # things from underdelivering. d.update({ 'CapType': 4, 'DailyCapAmount': int(math.ceil(total_budget_dollars / campaign.ndays)), 'LifetimeCapAmount': int(math.ceil(total_budget_dollars)), }) # Zerkel queries here if campaign.mobile_os: queries_list = [] if 'iOS' in campaign.mobile_os: ios_targets = get_mobile_targeting_query( os_str='iOS', lookup_str='modelName', devices=campaign.ios_devices, versions=campaign.ios_version_range) queries_list.append(ios_targets) if 'Android' in campaign.mobile_os: android_targets = get_mobile_targeting_query( os_str='Android', lookup_str='formFactor', devices=campaign.android_devices, versions=campaign.android_version_range) queries_list.append(android_targets) if campaign.platform == 'all': queries_list.append('($device.formFactor CONTAINS "desktop")') mobile_targeting_query = ' OR '.join(queries_list) d.update({ 'CustomTargeting': mobile_targeting_query, }) else: d.update({ 'CustomTargeting': '', }) if campaign.platform != 'all': d.update({ 'SiteZoneTargeting': [{ 'SiteId': g.az_selfserve_site_ids[campaign.platform], 'IsExclude': False, }], }) # special handling for location conversions between reddit and adzerk if campaign.location: campaign_country = campaign.location.country campaign_region = campaign.location.region if campaign.location.metro: campaign_metro = int(campaign.location.metro) else: campaign_metro = None if az_flight and az_flight.GeoTargeting: # special handling for geotargeting of existing flights # can't update geotargeting through the Flight endpoint, do it manually existing = az_flight.GeoTargeting[0] az_geotarget = adzerk_api.GeoTargeting._from_item(existing) if (campaign.location and (campaign_country != az_geotarget.CountryCode or campaign_region != az_geotarget.Region or campaign_metro != az_geotarget.MetroCode or az_geotarget.IsExclude)): # existing geotargeting doesn't match current location az_geotarget.CountryCode = campaign_country az_geotarget.Region = campaign_region az_geotarget.MetroCode = campaign_metro az_geotarget.IsExclude = False az_geotarget._send(az_flight.Id) log_text = 'updated geotargeting to %s' % campaign.location PromotionLog.add(link, log_text) elif not campaign.location: # flight should no longer be geotargeted az_geotarget._delete(az_flight.Id) log_text = 'deleted geotargeting' PromotionLog.add(link, log_text) # only allow one geotarget per flight for existing in az_flight.GeoTargeting[1:]: az_geotarget = adzerk_api.GeoTargeting._from_item(existing) az_geotarget._delete(az_flight.Id) # NOTE: need to unset GeoTargeting otherwise it will be added to the # flight again when we _send updates az_flight.GeoTargeting = None elif campaign.location: # flight endpoint works when a new flight is being created or an # existing one that didn't have geotargeting is being updated d.update({ 'GeoTargeting': [{ 'CountryCode': campaign_country, 'Region': campaign_region, 'MetroCode': campaign_metro, 'IsExclude': False, }], }) else: # no geotargeting, either a new flight is being created or an existing # flight is being updated that wasn't geotargeted d.update({ 'GeoTargeting': [], }) if az_flight: changed = update_changed(az_flight, **d) change_strs = make_change_strings(changed) if campaign_overdelivered: billable = promote.get_billable_impressions(campaign) over_str = 'overdelivered %s/%s' % (billable, campaign.impressions) change_strs.append(over_str) if change_strs: log_text = 'updated %s: ' % az_flight + ', '.join(change_strs) else: log_text = None else: d.update({'Name': campaign._fullname}) az_flight = adzerk_api.Flight.create(**d) campaign.external_flight_id = az_flight.Id campaign._commit() PromoCampaignByFlightIdCache.add(campaign) log_text = 'created %s' % az_flight if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) if campaign_overdelivered: campaign.external_flight_overdelivered = True campaign._commit() return az_flight