Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RON support and Bidder Code Params #52

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,14 @@ Setting | Description | Default
`DFP_USE_EXISTING_ORDER_IF_EXISTS` | Whether we should modify an existing order if one already exists with name `DFP_ORDER_NAME` | `False`
`DFP_NUM_CREATIVES_PER_LINE_ITEM` | The number of duplicate creatives to attach to each line item. Due to [DFP limitations](https://support.google.com/dfp_sb/answer/82245?hl=en), this should be equal to or greater than the number of ad units you serve on a given page. | the length of setting `DFP_TARGETED_PLACEMENT_NAMES`
`DFP_CURRENCY_CODE` | The currency to use in line items. | `'USD'`
`DFP_ALLOW_NO_INVENTORY_TARGETING` | If no placement should be used, for example for a run of network. If True, DFP_TARGETED_PLACEMENT_NAMES still need to be set to an empty array. | `False`
`DFP_ASSOCIATIONS_BATCH` | Determine number of line item/creative associations to be created in one batch. | the number of line items to be created multiplied by `DFP_NUM_CREATIVES_PER_LINE_ITEM`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be a setting. We can simply always use batch updates.

`PREBID_BIDDER_PARAMS` | Whether DFP targeting keys should be created following Bidders' Params structure. This is used when it's required to send all bids to the ad server. | `False`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about renaming this setting to PREBID_SEND_ALL_BIDS? I think that's a little clearer.


## Limitations

* Currently, the names of the bidder code targeting key (`hb_bidder`) and price bucket targeting key (`hb_pb`) are not customizable. The `hb_bidder` targeting key is currently required (see [#18](../../issues/18))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep the "and price bucket targeting key (hb_pb) are not customizable" because this PR still doesn't allow naming hb_pb to anything arbitrary.

* This tool does not support additional line item targeting beyond placement, `hb_bidder`, and `hb_pb` values. Placement targeting is currently required (see [#16](../../issues/16)), and targeting by ad unit isn't supported (see [#17](../../issues/17))
* Currently, the names of the bidder code targeting key (`hb_bidder`) is not customizable. The `hb_bidder` targeting key is currently required (see [#18](../../issues/18))
* This tool does not support additional line item targeting beyond placement, `hb_bidder`, and `hb_pb` values. Targeting by ad unit isn't supported (see [#17](../../issues/17))
* The price bucketing setting `PREBID_PRICE_BUCKETS` only allows for uniform bucketing. For example, you can create $0.01 buckets from $0 - $20, but you cannot specify $0.01 buckets from $0 - $5 and $0.50 buckets from $5 - $20. Using entirely $0.01 buckets will still work for the custom buckets—you'll just have more line items than you need.
* This tool does not modify existing orders or line items, it only creates them. If you need to make a change to an order, it's easiest to archive the existing order and recreate it.

Expand Down
44 changes: 39 additions & 5 deletions dfp/associate_line_items_and_creatives.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

from dfp.client import get_client

import settings
import time
import suds
from pprint import pprint
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove unused pprint


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -41,10 +45,40 @@ def make_licas(line_item_ids, creative_ids, size_overrides=[]):
# settings, as recommended: http://prebid.org/adops/step-by-step.html
'sizes': sizes
})
licas = lica_service.createLineItemCreativeAssociations(licas)

if licas:
logger.info(
u'Created {0} line item <> creative associations.'.format(len(licas)))
associations_batch = getattr(settings, 'DFP_ASSOCIATIONS_BATCH', None)

if not associations_batch is None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, let's just always use batch uploads here. Do you know if DFP has any batch size limits? I didn't see any. Hardcoding something like 20 or 50 items seems reasonable (whatever you found works for you).

while licas:
batch = []

for b in range(0, associations_batch):
if licas:
batch.append(licas.pop(0))

try:
time.sleep(1)
batch = lica_service.createLineItemCreativeAssociations(batch)
except suds.WebFault as err:
logger.info(u'A common error was raised (it can happen). Waiting 30 seconds and retrying...')
time.sleep(30)
try:
batch = lica_service.createLineItemCreativeAssociations(batch)
except suds.WebFault as err:
logger.info(u'A common error was raised (it can happen). Waiting 30 seconds and retrying...')
time.sleep(30)
batch = lica_service.createLineItemCreativeAssociations(batch)

if batch:
logger.info(
u'Created {0} line item <> creative associations.'.format(len(batch)))
else:
logger.info(u'No line item <> creative associations created.')
else:
logger.info(u'No line item <> creative associations created.')
licas = lica_service.createLineItemCreativeAssociations(licas)

if licas:
logger.info(
u'Created {0} line item <> creative associations.'.format(len(licas)))
else:
logger.info(u'No line item <> creative associations created.')
15 changes: 15 additions & 0 deletions dfp/create_creatives.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from dfp.client import get_client

import settings


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -48,6 +50,19 @@ def create_creative_config(name, advertiser_id):
with open(snippet_file_path, 'r') as snippet_file:
snippet = snippet_file.read()

# Determine what bidder params should be
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this logic into a standalone module (e.g. prebid_utils.py) and reuse it elsewhere.

bidder_code = getattr(settings, 'PREBID_BIDDER_CODE', None)
bidder_params = getattr(settings, 'PREBID_BIDDER_PARAMS', None)
hb_adid_key = 'hb_adid'

if bidder_params is True:
hb_adid_key += '_' + bidder_code

if len(hb_adid_key) > 20:
hb_adid_key = hb_adid_key[:20]

snippet = snippet.replace('{hb_adid_key}', hb_adid_key)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a test for this, if possible?


# https://developers.google.com/doubleclick-publishers/docs/reference/v201802/CreativeService.Creative
config = {
'xsi_type': 'ThirdPartyCreative',
Expand Down
41 changes: 28 additions & 13 deletions dfp/create_line_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from dfp.client import get_client

import settings

def create_line_items(line_items):
"""
Expand All @@ -25,7 +26,7 @@ def create_line_items(line_items):

def create_line_item_config(name, order_id, placement_ids, cpm_micro_amount,
sizes, hb_bidder_key_id, hb_pb_key_id, hb_bidder_value_id, hb_pb_value_id,
currency_code='USD'):
currency_code='USD', root_ad_unit_id=None):
"""
Creates a line item config object.

Expand Down Expand Up @@ -56,27 +57,43 @@ def create_line_item_config(name, order_id, placement_ids, cpm_micro_amount,
# https://github.com/googleads/googleads-python-lib/blob/master/examples/dfp/v201802/line_item_service/target_custom_criteria.py
# create custom criterias

hb_bidder_criteria = {
'xsi_type': 'CustomCriteria',
'keyId': hb_bidder_key_id,
'valueIds': [hb_bidder_value_id],
'operator': 'IS'
inventory = {
'targetedPlacementIds': placement_ids
}

if not root_ad_unit_id is None:
inventory = {
'targetedAdUnits': [{
'adUnitId': root_ad_unit_id,
'includeDescendants': 'true'
}]
}

hb_pb_criteria = {
'xsi_type': 'CustomCriteria',
'keyId': hb_pb_key_id,
'valueIds': [hb_pb_value_id],
'operator': 'IS'
}

# The custom criteria will resemble:
# (hb_bidder_criteria.key == hb_bidder_criteria.value AND
# hb_pb_criteria.key == hb_pb_criteria.value)
children = [hb_pb_criteria]

bidder_params = getattr(settings, 'PREBID_BIDDER_PARAMS', None)

if not bidder_params is True:
hb_bidder_criteria = {
'xsi_type': 'CustomCriteria',
'keyId': hb_bidder_key_id,
'valueIds': [hb_bidder_value_id],
'operator': 'IS'
}

children.append(hb_bidder_criteria)

top_set = {
'xsi_type': 'CustomCriteriaSet',
'logicalOperator': 'AND',
'children': [hb_bidder_criteria, hb_pb_criteria]
'children': children
}

# https://developers.google.com/doubleclick-publishers/docs/reference/v201802/LineItemService.LineItem
Expand All @@ -85,9 +102,7 @@ def create_line_item_config(name, order_id, placement_ids, cpm_micro_amount,
'orderId': order_id,
# https://developers.google.com/doubleclick-publishers/docs/reference/v201802/LineItemService.Targeting
'targeting': {
'inventoryTargeting': {
'targetedPlacementIds': placement_ids
},
'inventoryTargeting': inventory,
'customTargeting': top_set,
},
'startDateTimeType': 'IMMEDIATELY',
Expand Down
2 changes: 1 addition & 1 deletion dfp/creative_snippet.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
w = w.parent;
if (w.pbjs) {
try {
w.pbjs.renderAd(document, '%%PATTERN:hb_adid%%');
w.pbjs.renderAd(document, '%%PATTERN:{hb_adid_key}%%');
break;
} catch (e) {
continue;
Expand Down
68 changes: 68 additions & 0 deletions dfp/get_ad_units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging

from googleads import dfp

from dfp.client import get_client


logger = logging.getLogger(__name__)

def get_all_ad_units(print_ad_units=False):
"""
Gets ad units from DFP.

Returns:
array of ad units
"""

# Initialize units array
ad_units = []

dfp_client = get_client()

# Initialize appropriate service.
ad_unit_service = dfp_client.GetService('InventoryService', version='v201802')

# Create a statement to select ad units.
statement = dfp.StatementBuilder()

# Retrieve a small amount of ad units at a time, paging
# through until all ad units have been retrieved.
while True:
response = ad_unit_service.getAdUnitsByStatement(statement.ToStatement())
if 'results' in response:
for ad_unit in response['results']:
ad_units.append(ad_unit)
if print_ad_units:
print('Ad unit with ID "%s" and name "%s" was found.' % (ad_unit['id'], ad_unit['name']))
statement.offset += dfp.SUGGESTED_PAGE_LIMIT
else:
break

return ad_units

def get_root_ad_unit_id():
"""
Gets root ad unit ID from DFP.

Returns:
an ad unit ID, or None
"""

dfp_client = get_client()
network_service = dfp_client.GetService('NetworkService', version='v201802')
current_network = network_service.getCurrentNetwork()

if hasattr(current_network, 'effectiveRootAdUnitId'):
return current_network.effectiveRootAdUnitId

return None

def main():
get_all_ad_units(print_ad_units=True)

if __name__ == '__main__':
main()
19 changes: 17 additions & 2 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# The exact name of the DFP advertiser for the created order
DFP_ADVERTISER_NAME = None

# Names of placements the line items should target.
# Names of placements the line items should target. Has priority over ad units.
DFP_TARGETED_PLACEMENT_NAMES = []

# Sizes of placements. These are used to set line item and creative sizes.
Expand All @@ -33,6 +33,10 @@
},
]

# If no placement should be used, for example for a run of network. If True,
# DFP_TARGETED_PLACEMENT_NAMES still need to be set to an empty array.
DFP_ALLOW_NO_INVENTORY_TARGETING = False

# Whether we should create the advertiser in DFP if it does not exist.
# If False, the program will exit rather than create an advertiser.
DFP_CREATE_ADVERTISER_IF_DOES_NOT_EXIST = False
Expand All @@ -58,12 +62,23 @@
# The currency to use in DFP when setting line item CPMs. Defaults to 'USD'.
# DFP_CURRENCY_CODE = 'USD'

# Optional
# Determine if line items and creative should be associated in batch.
# Useful to avoid timeouts if many of them have to be created.
# DFP_ASSOCIATIONS_BATCH = 50
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, let's not add this setting.


#########################################################################
# PREBID SETTINGS
#########################################################################

PREBID_BIDDER_CODE = None

# Whether DFP targeting keys should be created following Bidders' Params structure.
# This is used when it's required to send all bids to the ad server.
# See: http://prebid.org/dev-docs/bidders.html
# And: http://prebid.org/adops/send-all-bids-adops.html
PREBID_BIDDER_PARAMS = False

# Price buckets. This should match your Prebid settings for the partner. See:
# http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.setPriceGranularity
# FIXME: this should be an array of buckets. See:
Expand All @@ -81,4 +96,4 @@
try:
from local_settings import *
except ImportError:
pass
pass
Loading