mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-27 07:48:16 -05:00
Add priorities to PromoCampaign.
This commit is contained in:
@@ -78,6 +78,7 @@ from r2.lib.validator import (
|
||||
VLink,
|
||||
VModhash,
|
||||
VOneOf,
|
||||
VPriority,
|
||||
VPromoCampaign,
|
||||
VRatelimit,
|
||||
VSelfText,
|
||||
@@ -129,6 +130,9 @@ def _check_dates(dates):
|
||||
|
||||
|
||||
def campaign_has_oversold_error(form, campaign):
|
||||
if campaign.priority.inventory_override:
|
||||
return
|
||||
|
||||
target = Subreddit._by_name(campaign.sr_name) if campaign.sr_name else None
|
||||
return has_oversold_error(form, campaign._id, campaign.start_date,
|
||||
campaign.end_date, campaign.bid, campaign.cpm,
|
||||
@@ -184,6 +188,17 @@ class PromoteController(ListingController):
|
||||
sr_names = sorted([sr.name for sr in srs], key=lambda s: s.lower())
|
||||
return sr_names
|
||||
|
||||
@classmethod
|
||||
@memoize('house_campaigns', time=60)
|
||||
def get_house_campaigns(cls):
|
||||
now = promote.promo_datetime_now()
|
||||
pws = PromotionWeights.get_campaigns(now)
|
||||
campaign_ids = {pw.promo_idx for pw in pws}
|
||||
campaigns = PromoCampaign._byID(campaign_ids, data=True,
|
||||
return_dict=False)
|
||||
campaigns = [camp for camp in campaigns if not camp.priority.cpm]
|
||||
return campaigns
|
||||
|
||||
@property
|
||||
def menus(self):
|
||||
filters = [
|
||||
@@ -240,6 +255,10 @@ class PromoteController(ListingController):
|
||||
return [Link._fullname_from_id36(to36(id)) for id in link_ids]
|
||||
elif self.sort == 'reported':
|
||||
return queries.get_reported_links(get_promote_srid())
|
||||
elif self.sort == 'house':
|
||||
campaigns = self.get_house_campaigns()
|
||||
link_ids = {camp.link_id for camp in campaigns}
|
||||
return [Link._fullname_from_id36(to36(id)) for id in link_ids]
|
||||
return queries.get_all_promoted_links()
|
||||
else:
|
||||
if self.sort == "future_promos":
|
||||
@@ -558,9 +577,10 @@ class PromoteController(ListingController):
|
||||
coerce=False, error=errors.BAD_BID),
|
||||
sr=VSubmitSR('sr', promotion=True),
|
||||
campaign_id36=nop("campaign_id36"),
|
||||
targeting=VLength("targeting", 10))
|
||||
targeting=VLength("targeting", 10),
|
||||
priority=VPriority("priority"))
|
||||
def POST_edit_campaign(self, form, jquery, link, campaign_id36,
|
||||
dates, bid, sr, targeting):
|
||||
dates, bid, sr, targeting, priority):
|
||||
if not link:
|
||||
return
|
||||
|
||||
@@ -601,29 +621,34 @@ class PromoteController(ListingController):
|
||||
form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
|
||||
return
|
||||
|
||||
if form.has_errors('bid', errors.BAD_BID):
|
||||
return
|
||||
|
||||
campaign = None
|
||||
if campaign_id36:
|
||||
# you cannot edit the bid of a live ad unless it's a freebie
|
||||
try:
|
||||
campaign = PromoCampaign._byID36(campaign_id36)
|
||||
if (bid != campaign.bid and
|
||||
campaign.start_date < datetime.now(g.tz) and
|
||||
not campaign.is_freebie()):
|
||||
c.errors.add(errors.BID_LIVE, field='bid')
|
||||
form.has_errors('bid', errors.BID_LIVE)
|
||||
return
|
||||
except NotFound:
|
||||
pass
|
||||
|
||||
min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
|
||||
if bid is None or bid < min_bid:
|
||||
c.errors.add(errors.BAD_BID, field='bid',
|
||||
msg_params={'min': min_bid,
|
||||
'max': g.max_promote_bid})
|
||||
form.has_errors('bid', errors.BAD_BID)
|
||||
return
|
||||
if priority.cpm:
|
||||
if form.has_errors('bid', errors.BAD_BID):
|
||||
return
|
||||
|
||||
# you cannot edit the bid of a live ad unless it's a freebie
|
||||
if (campaign and bid != campaign.bid and
|
||||
campaign.start_date < datetime.now(g.tz) and
|
||||
not campaign.is_freebie()):
|
||||
c.errors.add(errors.BID_LIVE, field='bid')
|
||||
form.has_errors('bid', errors.BID_LIVE)
|
||||
return
|
||||
|
||||
min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
|
||||
if bid is None or bid < min_bid:
|
||||
c.errors.add(errors.BAD_BID, field='bid',
|
||||
msg_params={'min': min_bid,
|
||||
'max': g.max_promote_bid})
|
||||
form.has_errors('bid', errors.BAD_BID)
|
||||
return
|
||||
else:
|
||||
bid = 0. # Set bid to 0 as dummy value
|
||||
|
||||
if targeting == 'one':
|
||||
if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
|
||||
@@ -646,23 +671,24 @@ class PromoteController(ListingController):
|
||||
sr = None
|
||||
|
||||
# Check inventory
|
||||
campaign_id = campaign._id if campaign_id36 else None
|
||||
if has_oversold_error(form, campaign_id, start, end, bid, cpm, sr):
|
||||
campaign_id = campaign._id if campaign else None
|
||||
if (not priority.inventory_override and
|
||||
has_oversold_error(form, campaign_id, start, end, bid, cpm, sr)):
|
||||
return
|
||||
|
||||
if campaign_id36 is not None:
|
||||
campaign = PromoCampaign._byID36(campaign_id36)
|
||||
promote.edit_campaign(link, campaign, dates, bid, cpm, sr)
|
||||
if campaign:
|
||||
promote.edit_campaign(link, campaign, dates, bid, cpm, sr, priority)
|
||||
r = promote.get_renderable_campaigns(link, campaign)
|
||||
jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
|
||||
r.duration, r.bid, r.spent, r.cpm,
|
||||
r.sr, r.status)
|
||||
r.duration, r.bid, r.spent, r.cpm, r.sr,
|
||||
r.priority_name, r.inventory_override,
|
||||
r.status)
|
||||
else:
|
||||
campaign = promote.new_campaign(link, dates, bid, cpm, sr)
|
||||
campaign = promote.new_campaign(link, dates, bid, cpm, sr, priority)
|
||||
r = promote.get_renderable_campaigns(link, campaign)
|
||||
jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
|
||||
r.duration, r.bid, r.spent, r.cpm,
|
||||
r.sr, r.status)
|
||||
r.duration, r.bid, r.spent, r.cpm, r.sr,
|
||||
r.priority_name, r.inventory_override, r.status)
|
||||
|
||||
@validatedForm(VSponsor('link_id'),
|
||||
VModhash(),
|
||||
|
||||
@@ -37,7 +37,12 @@ from r2.models.gold import (
|
||||
days_to_pennies,
|
||||
gold_revenue_on,
|
||||
)
|
||||
from r2.models.promo import NO_TRANSACTION, PromotionLog, PromotedLinkRoadblock
|
||||
from r2.models.promo import (
|
||||
NO_TRANSACTION,
|
||||
PROMOTE_PRIORITIES,
|
||||
PromotedLinkRoadblock,
|
||||
PromotionLog,
|
||||
)
|
||||
from r2.models.token import OAuth2Client, OAuth2AccessToken
|
||||
from r2.models import traffic
|
||||
from r2.models import ModAction
|
||||
@@ -3425,6 +3430,7 @@ class PromotePage(Reddit):
|
||||
dest='/admin/graph'))
|
||||
buttons.append(NavButton('report', 'report'))
|
||||
buttons.append(NavButton('underdelivered', 'underdelivered'))
|
||||
buttons.append(NavButton('house ads', 'house'))
|
||||
buttons.append(NavButton('reported links', 'reported'))
|
||||
|
||||
menu = NavMenu(buttons, base_path = '/promoted',
|
||||
@@ -3510,6 +3516,9 @@ class PromoteLinkForm(Templated):
|
||||
|
||||
self.min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
|
||||
|
||||
self.priorities = [(p.name, p.text, p.description, p.default, p.inventory_override, p.cpm)
|
||||
for p in sorted(PROMOTE_PRIORITIES.values(), key=lambda p: p.value)]
|
||||
|
||||
# preload some inventory
|
||||
srnames = set()
|
||||
for title, names in self.subreddit_selector.subreddit_names:
|
||||
|
||||
@@ -186,15 +186,23 @@ def campaign_is_live(link, campaign_index):
|
||||
|
||||
class RenderableCampaign():
|
||||
def __init__(self, campaign_id36, start_date, end_date, duration, bid,
|
||||
spent, cpm, sr, status):
|
||||
spent, cpm, sr, priority, status):
|
||||
self.campaign_id36 = campaign_id36
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
self.duration = duration
|
||||
self.bid = "%.2f" % bid
|
||||
self.spent = "%.2f" % spent
|
||||
|
||||
if priority.cpm:
|
||||
self.bid = "%.2f" % bid
|
||||
self.spent = "%.2f" % spent
|
||||
else:
|
||||
self.bid = "N/A"
|
||||
self.spent = "N/A"
|
||||
|
||||
self.cpm = cpm
|
||||
self.sr = sr
|
||||
self.priority_name = priority.name
|
||||
self.inventory_override = priority.inventory_override
|
||||
self.status = status
|
||||
|
||||
@classmethod
|
||||
@@ -228,7 +236,8 @@ class RenderableCampaign():
|
||||
'pay_url': pay_url(link, camp),
|
||||
'view_live_url': view_live_url(link, sr),
|
||||
'sponsor': user_is_sponsor,
|
||||
'live': live}
|
||||
'live': live,
|
||||
'non_cpm': not camp.priority.cpm}
|
||||
|
||||
if transaction and transaction.is_void():
|
||||
status['paid'] = False
|
||||
@@ -240,7 +249,7 @@ class RenderableCampaign():
|
||||
status['refund_url'] = refund_url(link, camp)
|
||||
|
||||
rc = cls(campaign_id36, start_date, end_date, duration, bid, spent,
|
||||
cpm, sr, status)
|
||||
cpm, sr, camp.priority, status)
|
||||
r.append(rc)
|
||||
return r
|
||||
|
||||
@@ -353,22 +362,28 @@ def get_transactions(link, campaigns):
|
||||
bids_by_campaign = {c._id: bid_dict[(c._id, c.trans_id)] for c in campaigns}
|
||||
return bids_by_campaign
|
||||
|
||||
def new_campaign(link, dates, bid, cpm, sr):
|
||||
def new_campaign(link, dates, bid, cpm, sr, priority):
|
||||
# empty string for sr_name means target to all
|
||||
sr_name = sr.name if sr else ""
|
||||
campaign = PromoCampaign._new(link, sr_name, bid, cpm, dates[0], dates[1])
|
||||
campaign = PromoCampaign._new(link, sr_name, bid, cpm, dates[0], dates[1],
|
||||
priority)
|
||||
PromotionWeights.add(link, campaign._id, sr_name, dates[0], dates[1], bid)
|
||||
PromotionLog.add(link, 'campaign %s created' % campaign._id)
|
||||
author = Account._byID(link.author_id, data=True)
|
||||
if getattr(author, "complimentary_promos", False):
|
||||
free_campaign(link, campaign, c.user)
|
||||
|
||||
if campaign.priority.cpm:
|
||||
author = Account._byID(link.author_id, data=True)
|
||||
if getattr(author, "complimentary_promos", False):
|
||||
free_campaign(link, campaign, c.user)
|
||||
else:
|
||||
# non-cpm campaigns are never charged, so we need to fire the hook now
|
||||
hooks.get_hook('promote.new_charge').call(link=link, campaign=campaign)
|
||||
return campaign
|
||||
|
||||
|
||||
def free_campaign(link, campaign, user):
|
||||
auth_campaign(link, campaign, user, -1)
|
||||
|
||||
def edit_campaign(link, campaign, dates, bid, cpm, sr):
|
||||
def edit_campaign(link, campaign, dates, bid, cpm, sr, priority):
|
||||
sr_name = sr.name if sr else '' # empty string means target to all
|
||||
try:
|
||||
# if the bid amount changed, cancel any pending transactions
|
||||
@@ -381,16 +396,17 @@ def edit_campaign(link, campaign, dates, bid, cpm, sr):
|
||||
|
||||
# update values in the db
|
||||
campaign.update(dates[0], dates[1], bid, cpm, sr_name,
|
||||
campaign.trans_id, commit=True)
|
||||
campaign.trans_id, priority, commit=True)
|
||||
|
||||
# record the transaction
|
||||
text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid)
|
||||
PromotionLog.add(link, text)
|
||||
if campaign.priority.cpm:
|
||||
# record the transaction
|
||||
text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid)
|
||||
PromotionLog.add(link, text)
|
||||
|
||||
# make it a freebie, if applicable
|
||||
author = Account._byID(link.author_id, True)
|
||||
if getattr(author, "complimentary_promos", False):
|
||||
free_campaign(link, campaign, c.user)
|
||||
# make it a freebie, if applicable
|
||||
author = Account._byID(link.author_id, True)
|
||||
if getattr(author, "complimentary_promos", False):
|
||||
free_campaign(link, campaign, c.user)
|
||||
|
||||
hooks.get_hook('campaign.edit').call(link=link, campaign=campaign)
|
||||
|
||||
@@ -572,7 +588,7 @@ def accepted_campaigns(offset=0):
|
||||
campaigns = dict((camp._id, camp) for camp in campaign_query)
|
||||
for pw in promo_weights:
|
||||
campaign = campaigns.get(pw.promo_idx)
|
||||
if not campaign or not campaign.trans_id:
|
||||
if not campaign or (not campaign.trans_id and campaign.priority.cpm):
|
||||
continue
|
||||
link = accepted_links.get(campaign.link_id)
|
||||
if not link:
|
||||
@@ -580,6 +596,14 @@ def accepted_campaigns(offset=0):
|
||||
|
||||
yield (link, campaign, pw.weight)
|
||||
|
||||
|
||||
def charged_or_not_needed(campaign):
|
||||
# True if a campaign has a charged transaction or doesn't need one
|
||||
charged = authorize.is_charged_transaction(campaign.trans_id, campaign._id)
|
||||
needs_charge = campaign.priority.cpm
|
||||
return charged or not needs_charge
|
||||
|
||||
|
||||
def get_scheduled(offset=0):
|
||||
"""
|
||||
Arguments:
|
||||
@@ -596,7 +620,7 @@ def get_scheduled(offset=0):
|
||||
error_campaigns = []
|
||||
for l, campaign, weight in accepted_campaigns(offset=offset):
|
||||
try:
|
||||
if authorize.is_charged_transaction(campaign.trans_id, campaign._id):
|
||||
if charged_or_not_needed(campaign):
|
||||
adweight = AdWeight(l._fullname, weight, campaign._fullname)
|
||||
adweights.append(adweight)
|
||||
except Exception, e: # could happen if campaign things have corrupt data
|
||||
@@ -614,8 +638,7 @@ def charge_pending(offset=1):
|
||||
for l, camp, weight in accepted_campaigns(offset=offset):
|
||||
user = Account._byID(l.author_id)
|
||||
try:
|
||||
if authorize.is_charged_transaction(camp.trans_id, camp._id):
|
||||
# already charged
|
||||
if charged_or_not_needed(camp):
|
||||
continue
|
||||
|
||||
charge_succeeded = authorize.charge_transaction(user, camp.trans_id,
|
||||
@@ -641,7 +664,7 @@ def charge_pending(offset=1):
|
||||
def scheduled_campaigns_by_link(l, date=None):
|
||||
# A promotion/campaign is scheduled/live if it's in
|
||||
# PromotionWeights.get_campaigns(now) and
|
||||
# authorize.is_charged_transaction()
|
||||
# charged_or_not_needed
|
||||
|
||||
date = date or promo_datetime_now()
|
||||
|
||||
@@ -656,7 +679,7 @@ def scheduled_campaigns_by_link(l, date=None):
|
||||
for campaign_id in campaigns:
|
||||
try:
|
||||
campaign = PromoCampaign._byID(campaign_id, data=True)
|
||||
if authorize.is_charged_transaction(campaign.trans_id, campaign_id):
|
||||
if charged_or_not_needed(campaign):
|
||||
accepted.append(campaign_id)
|
||||
except NotFound:
|
||||
g.log.error("PromoCampaign %d scheduled to run on %s not found." %
|
||||
|
||||
@@ -1683,6 +1683,15 @@ class VOneOf(Validator):
|
||||
for s in self.options),
|
||||
}
|
||||
|
||||
|
||||
class VPriority(Validator):
|
||||
def run(self, val):
|
||||
if c.user_is_sponsor:
|
||||
return PROMOTE_PRIORITIES.get(val, PROMOTE_DEFAULT_PRIORITY)
|
||||
else:
|
||||
return PROMOTE_DEFAULT_PRIORITY
|
||||
|
||||
|
||||
class VImageType(Validator):
|
||||
def run(self, img_type):
|
||||
if not img_type in ('png', 'jpg'):
|
||||
|
||||
@@ -27,6 +27,7 @@ import json
|
||||
|
||||
from pycassa.types import CompositeType
|
||||
from pylons import g, c
|
||||
from pylons.i18n import _, N_
|
||||
|
||||
from r2.lib import filters
|
||||
from r2.lib.cache import sgm
|
||||
@@ -40,6 +41,60 @@ from r2.models.subreddit import Subreddit
|
||||
PROMOTE_STATUS = Enum("unpaid", "unseen", "accepted", "rejected",
|
||||
"pending", "promoted", "finished")
|
||||
|
||||
class PriorityLevel(object):
|
||||
name = ''
|
||||
_text = N_('')
|
||||
_description = N_('')
|
||||
value = 1 # Values are from 1 (highest) to 100 (lowest)
|
||||
default = False
|
||||
inventory_override = False
|
||||
cpm = True # Non-cpm is percentage, will fill unsold impressions
|
||||
|
||||
def __repr__(self):
|
||||
return "<PriorityLevel %s: %s>" % (self.name, self.value)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return _(self._text) if self._text else ''
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return _(self._description) if self._description else ''
|
||||
|
||||
|
||||
class HighPriority(PriorityLevel):
|
||||
name = 'high'
|
||||
_text = N_('highest')
|
||||
value = 5
|
||||
|
||||
|
||||
class MediumPriority(PriorityLevel):
|
||||
name = 'standard'
|
||||
_text = N_('standard')
|
||||
value = 10
|
||||
default = True
|
||||
|
||||
|
||||
class RemnantPriority(PriorityLevel):
|
||||
name = 'remnant'
|
||||
_text = N_('remnant')
|
||||
_description = N_('lower priority, impressions are not guaranteed')
|
||||
value = 20
|
||||
inventory_override = True
|
||||
|
||||
|
||||
class HousePriority(PriorityLevel):
|
||||
name = 'house'
|
||||
_text = N_('house')
|
||||
_description = N_('non-CPM, displays in all unsold impressions')
|
||||
value = 30
|
||||
inventory_override = True
|
||||
cpm = False
|
||||
|
||||
|
||||
HIGH, MEDIUM, REMNANT, HOUSE = HighPriority(), MediumPriority(), RemnantPriority(), HousePriority()
|
||||
PROMOTE_PRIORITIES = {p.name: p for p in (HIGH, MEDIUM, REMNANT, HOUSE)}
|
||||
PROMOTE_DEFAULT_PRIORITY = MEDIUM
|
||||
|
||||
@memoize("get_promote_srid")
|
||||
def get_promote_srid(name = 'promos'):
|
||||
@@ -64,6 +119,10 @@ def calc_impressions(bid, cpm_pennies):
|
||||
NO_TRANSACTION = 0
|
||||
|
||||
class PromoCampaign(Thing):
|
||||
_defaults = dict(
|
||||
priority_name=PROMOTE_DEFAULT_PRIORITY.name,
|
||||
)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
val = Thing.__getattr__(self, attr)
|
||||
if attr in ('start_date', 'end_date'):
|
||||
@@ -72,8 +131,14 @@ class PromoCampaign(Thing):
|
||||
val = val.replace(tzinfo=g.tz)
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
def get_priority_name(cls, priority):
|
||||
if not priority in PROMOTE_PRIORITIES.values():
|
||||
raise ValueError("%s is not a valid priority" % val)
|
||||
return priority.name
|
||||
|
||||
@classmethod
|
||||
def _new(cls, link, sr_name, bid, cpm, start_date, end_date):
|
||||
def _new(cls, link, sr_name, bid, cpm, start_date, end_date, priority):
|
||||
pc = PromoCampaign(link_id=link._id,
|
||||
sr_name=sr_name,
|
||||
bid=bid,
|
||||
@@ -81,7 +146,8 @@ class PromoCampaign(Thing):
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
trans_id=NO_TRANSACTION,
|
||||
owner_id=link.author_id)
|
||||
owner_id=link.author_id,
|
||||
priority_name=cls.get_priority_name(priority))
|
||||
pc._commit()
|
||||
return pc
|
||||
|
||||
@@ -111,8 +177,14 @@ class PromoCampaign(Thing):
|
||||
# deal with pre-CPM PromoCampaigns
|
||||
if not hasattr(self, 'cpm'):
|
||||
return -1
|
||||
elif not self.priority.cpm:
|
||||
return -1
|
||||
return calc_impressions(self.bid, self.cpm)
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
return PROMOTE_PRIORITIES[self.priority_name]
|
||||
|
||||
def is_freebie(self):
|
||||
return self.trans_id < 0
|
||||
|
||||
@@ -121,13 +193,14 @@ class PromoCampaign(Thing):
|
||||
return self.start_date < now and self.end_date > now
|
||||
|
||||
def update(self, start_date, end_date, bid, cpm, sr_name, trans_id,
|
||||
commit=True):
|
||||
priority, commit=True):
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
self.bid = bid
|
||||
self.cpm = cpm
|
||||
self.sr_name = sr_name
|
||||
self.trans_id = trans_id
|
||||
self.priority_name = self.get_priority_name(priority)
|
||||
if commit:
|
||||
self._commit()
|
||||
|
||||
|
||||
@@ -4521,6 +4521,7 @@ ul.tabmenu.formtab {
|
||||
font-size: small;
|
||||
padding: 4px;
|
||||
padding-top: 8px;
|
||||
width: 90px;
|
||||
}
|
||||
.linefield .campaign input[type=text] {
|
||||
font-size: x-small;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
r.sponsored = {
|
||||
init: function() {
|
||||
init: function(isSponsor) {
|
||||
$("#sr-autocomplete").on("sr-changed blur", function() {
|
||||
r.sponsored.fill_campaign_editor()
|
||||
})
|
||||
|
||||
this.isSponsor = isSponsor
|
||||
this.inventory = {}
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ r.sponsored = {
|
||||
}
|
||||
},
|
||||
|
||||
get_booked_inventory: function($form, srname) {
|
||||
get_booked_inventory: function($form, srname, isOverride) {
|
||||
var campaign_id36 = $form.find('input[name="campaign_id36"]').val(),
|
||||
campaign_row = $('.existing-campaigns .campaign-row input[name="campaign_id36"]')
|
||||
.filter('*[value="' + campaign_id36 + '"]')
|
||||
@@ -83,6 +83,11 @@ r.sponsored = {
|
||||
return {}
|
||||
}
|
||||
|
||||
var existingOverride = campaign_row.find('*[name="override"]').val()
|
||||
if (isOverride != existingOverride) {
|
||||
return {}
|
||||
}
|
||||
|
||||
var startdate = campaign_row.find('*[name="startdate"]').val(),
|
||||
enddate = campaign_row.find('*[name="enddate"]').val(),
|
||||
dates = this.get_dates(startdate, enddate),
|
||||
@@ -101,7 +106,7 @@ r.sponsored = {
|
||||
|
||||
},
|
||||
|
||||
check_inventory: function($form) {
|
||||
check_inventory: function($form, isOverride) {
|
||||
var bid = this.get_bid($form),
|
||||
cpm = this.get_cpm($form),
|
||||
requested = this.calc_impressions(bid, cpm),
|
||||
@@ -113,7 +118,7 @@ r.sponsored = {
|
||||
target = $form.find('*[name="sr"]').val(),
|
||||
srname = targeted ? target : '',
|
||||
dates = r.sponsored.get_dates(startdate, enddate),
|
||||
booked = this.get_booked_inventory($form, srname)
|
||||
booked = this.get_booked_inventory($form, srname, isOverride)
|
||||
|
||||
// bail out in state where targeting is selected but srname
|
||||
// has not been entered yet
|
||||
@@ -124,32 +129,56 @@ r.sponsored = {
|
||||
|
||||
$.when(r.sponsored.get_check_inventory(srname, dates)).done(
|
||||
function() {
|
||||
var minDaily = _.min(_.map(dates, function(date) {
|
||||
var datestr = $.datepicker.formatDate('mm/dd/yy', date),
|
||||
daily_booked = booked[datestr] || 0
|
||||
return r.sponsored.inventory[srname][datestr] + daily_booked
|
||||
}))
|
||||
if (isOverride) {
|
||||
// do a simple sum of available inventory for override
|
||||
var available = _.reduce(_.map(dates, function(date){
|
||||
var datestr = $.datepicker.formatDate('mm/dd/yy', date),
|
||||
daily_booked = booked[datestr] || 0
|
||||
return r.sponsored.inventory[srname][datestr] + daily_booked
|
||||
}), function(memo, num){ return memo + num; }, 0)
|
||||
} else {
|
||||
// calculate conservative inventory estimate
|
||||
var minDaily = _.min(_.map(dates, function(date) {
|
||||
var datestr = $.datepicker.formatDate('mm/dd/yy', date),
|
||||
daily_booked = booked[datestr] || 0
|
||||
return r.sponsored.inventory[srname][datestr] + daily_booked
|
||||
}))
|
||||
var available = minDaily * ndays
|
||||
}
|
||||
|
||||
var available = minDaily * ndays,
|
||||
maxbid = r.sponsored.calc_bid(available, cpm)
|
||||
var maxbid = r.sponsored.calc_bid(available, cpm)
|
||||
|
||||
if (available < requested) {
|
||||
var message = r._("We have insufficient inventory to fulfill" +
|
||||
" your requested budget, target, and dates." +
|
||||
" Only %(available)s impressions available" +
|
||||
" on %(target)s from %(start)s to %(end)s. " +
|
||||
"Maximum budget is $%(max)s."
|
||||
).format({
|
||||
available: r.utils.prettyNumber(available),
|
||||
target: targeted ? srname : 'the frontpage',
|
||||
start: startdate,
|
||||
end: enddate,
|
||||
max: maxbid
|
||||
})
|
||||
if (isOverride) {
|
||||
var message = r._("We expect to only have %(available)s " +
|
||||
"impressions on %(target)s from %(start)s " +
|
||||
"to %(end)s. We may not fully deliver."
|
||||
).format({
|
||||
available: r.utils.prettyNumber(available),
|
||||
target: targeted ? srname : 'the frontpage',
|
||||
start: startdate,
|
||||
end: enddate
|
||||
})
|
||||
$(".available-info").text('')
|
||||
$(".OVERSOLD_DETAIL").text(message).show()
|
||||
} else {
|
||||
var message = r._("We have insufficient inventory to fulfill" +
|
||||
" your requested budget, target, and dates." +
|
||||
" Only %(available)s impressions available" +
|
||||
" on %(target)s from %(start)s to %(end)s. " +
|
||||
"Maximum budget is $%(max)s."
|
||||
).format({
|
||||
available: r.utils.prettyNumber(available),
|
||||
target: targeted ? srname : 'the frontpage',
|
||||
start: startdate,
|
||||
end: enddate,
|
||||
max: maxbid
|
||||
})
|
||||
|
||||
$(".available-info").text('')
|
||||
$(".OVERSOLD_DETAIL").text(message).show()
|
||||
r.sponsored.disable_form($form)
|
||||
$(".available-info").text('')
|
||||
$(".OVERSOLD_DETAIL").text(message).show()
|
||||
r.sponsored.disable_form($form)
|
||||
}
|
||||
} else {
|
||||
$(".available-info").text(r._("%(num)s available (maximum budget is $%(max)s)").format({num: r.utils.prettyNumber(available), max: maxbid}))
|
||||
$(".OVERSOLD_DETAIL").hide()
|
||||
@@ -200,7 +229,10 @@ r.sponsored = {
|
||||
bid = this.get_bid($form),
|
||||
cpm = this.get_cpm($form),
|
||||
ndays = this.get_duration($form),
|
||||
impressions = this.calc_impressions(bid, cpm);
|
||||
impressions = this.calc_impressions(bid, cpm),
|
||||
selected = $form.find('*[name="priority"]:checked'),
|
||||
isOverride = selected.attr("override") == "override",
|
||||
isCpm = selected.attr("cpm") == "cpm"
|
||||
|
||||
$(".duration").text(ndays + " " + ((ndays > 1) ? r._("days") : r._("day")))
|
||||
$(".price-info").text(r._("$%(cpm)s per 1,000 impressions").format({cpm: (cpm/100).toFixed(2)}))
|
||||
@@ -208,8 +240,14 @@ r.sponsored = {
|
||||
$(".OVERSOLD").hide()
|
||||
|
||||
this.enable_form($form)
|
||||
this.check_bid($form)
|
||||
this.check_inventory($form)
|
||||
|
||||
if (isCpm) {
|
||||
this.show_cpm()
|
||||
this.check_bid($form)
|
||||
this.check_inventory($form, isOverride)
|
||||
} else {
|
||||
this.hide_cpm()
|
||||
}
|
||||
},
|
||||
|
||||
disable_form: function($form) {
|
||||
@@ -224,6 +262,24 @@ r.sponsored = {
|
||||
.removeClass("disabled");
|
||||
},
|
||||
|
||||
hide_cpm: function() {
|
||||
var priceRow = $('#cpm').parent('td').parent('tr'),
|
||||
budgetRow = $('#bid').parent('td').parent('tr'),
|
||||
impressionsRow = $('#impressions').parent('td').parent('tr')
|
||||
priceRow.hide("slow")
|
||||
budgetRow.hide("slow")
|
||||
impressionsRow.hide("slow")
|
||||
},
|
||||
|
||||
show_cpm: function() {
|
||||
var priceRow = $('#cpm').parent('td').parent('tr'),
|
||||
budgetRow = $('#bid').parent('td').parent('tr'),
|
||||
impressionsRow = $('#impressions').parent('td').parent('tr')
|
||||
priceRow.show("slow")
|
||||
budgetRow.show("slow")
|
||||
impressionsRow.show("slow")
|
||||
},
|
||||
|
||||
targeting_on: function() {
|
||||
$('.targeting').find('*[name="sr"]').prop("disabled", "").end().slideDown();
|
||||
this.fill_campaign_editor()
|
||||
@@ -234,6 +290,10 @@ r.sponsored = {
|
||||
this.fill_campaign_editor()
|
||||
},
|
||||
|
||||
priority_changed: function() {
|
||||
this.fill_campaign_editor()
|
||||
},
|
||||
|
||||
check_bid: function($form) {
|
||||
var bid = this.get_bid($form),
|
||||
minimum_bid = $("#bid").data("min_bid");
|
||||
@@ -338,11 +398,14 @@ function get_flag_class(flags) {
|
||||
if (flags.refund) {
|
||||
css_class += " refund";
|
||||
}
|
||||
if (flags.non_cpm) {
|
||||
css_class += " non_cpm";
|
||||
}
|
||||
return css_class
|
||||
}
|
||||
|
||||
$.new_campaign = function(campaign_id36, start_date, end_date, duration,
|
||||
bid, spent, cpm, targeting, flags) {
|
||||
bid, spent, cpm, targeting, priority, override, flags) {
|
||||
cancel_edit(function() {
|
||||
var data =('<input type="hidden" name="startdate" value="' +
|
||||
start_date +'"/>' +
|
||||
@@ -352,6 +415,8 @@ $.new_campaign = function(campaign_id36, start_date, end_date, duration,
|
||||
'<input type="hidden" name="cpm" value="' + cpm + '"/>' +
|
||||
'<input type="hidden" name="targeting" value="' +
|
||||
(targeting || '') + '"/>' +
|
||||
'<input type="hidden" name="priority" value="' + priority + '"/>' +
|
||||
'<input type="hidden" name="override" value="' + override + '"/>' +
|
||||
'<input type="hidden" name="campaign_id36" value="' + campaign_id36 + '"/>');
|
||||
if (flags && flags.pay_url) {
|
||||
data += ("<input type='hidden' name='pay_url' value='" +
|
||||
@@ -365,7 +430,7 @@ $.new_campaign = function(campaign_id36, start_date, end_date, duration,
|
||||
data += ("<input type='hidden' name='refund_url' value='" +
|
||||
flags.refund_url + "'/>");
|
||||
}
|
||||
var row = [start_date, end_date, duration, "$" + bid, "$" + spent, targeting, data];
|
||||
var row = [start_date, end_date, duration, priority, "$" + bid, "$" + spent, targeting, data];
|
||||
$(".existing-campaigns .error").hide();
|
||||
var css_class = get_flag_class(flags);
|
||||
$(".existing-campaigns table").show()
|
||||
@@ -378,7 +443,8 @@ $.new_campaign = function(campaign_id36, start_date, end_date, duration,
|
||||
};
|
||||
|
||||
$.update_campaign = function(campaign_id36, start_date, end_date,
|
||||
duration, bid, spent, cpm, targeting, flags) {
|
||||
duration, bid, spent, cpm, targeting, priority,
|
||||
override, flags) {
|
||||
cancel_edit(function() {
|
||||
$('.existing-campaigns input[name="campaign_id36"]')
|
||||
.filter('*[value="' + (campaign_id36 || '0') + '"]')
|
||||
@@ -387,6 +453,7 @@ $.update_campaign = function(campaign_id36, start_date, end_date,
|
||||
.children(":first").html(start_date)
|
||||
.next().html(end_date)
|
||||
.next().html(duration)
|
||||
.next().html(priority)
|
||||
.next().html("$" + bid).removeClass()
|
||||
.next().html("$" + spent)
|
||||
.next().html(targeting)
|
||||
@@ -394,6 +461,8 @@ $.update_campaign = function(campaign_id36, start_date, end_date,
|
||||
.find('*[name="startdate"]').val(start_date).end()
|
||||
.find('*[name="enddate"]').val(end_date).end()
|
||||
.find('*[name="targeting"]').val(targeting).end()
|
||||
.find('*[name="priority"]').val(priority).end()
|
||||
.find('*[name="override"]').val(override).end()
|
||||
.find('*[name="bid"]').val(bid).end()
|
||||
.find('*[name="cpm"]').val(cpm).end()
|
||||
.find("button, span").remove();
|
||||
@@ -412,7 +481,7 @@ $.set_up_campaigns = function() {
|
||||
$(".existing-campaigns tr").each(function() {
|
||||
var tr = $(this);
|
||||
var td = $(this).find("td:last");
|
||||
var bid_td = $(this).find("td:first").next().next().next()
|
||||
var bid_td = $(this).find("td:first").next().next().next().next()
|
||||
.addClass("bid");
|
||||
if(td.length && ! td.children("button, span").length ) {
|
||||
if(tr.hasClass("live")) {
|
||||
@@ -427,20 +496,22 @@ $.set_up_campaigns = function() {
|
||||
|
||||
/* once paid, we shouldn't muck around with the campaign */
|
||||
if(!tr.hasClass("complete") && !tr.hasClass("live")) {
|
||||
if (tr.hasClass("sponsor") && !tr.hasClass("free")) {
|
||||
$(bid_td).append($(free).addClass("free")
|
||||
.click(function() { free_campaign(tr) }))
|
||||
}
|
||||
else if (!tr.hasClass("paid")) {
|
||||
$(bid_td).prepend($(pay).addClass("pay fancybutton")
|
||||
.click(function() { pay_campaign(tr) }));
|
||||
} else if (tr.hasClass("free")) {
|
||||
$(bid_td).addClass("free paid")
|
||||
.prepend("<span class='info'>freebie</span>");
|
||||
} else {
|
||||
(bid_td).addClass("paid")
|
||||
.prepend($(repay).addClass("pay fancybutton")
|
||||
.click(function() { pay_campaign(tr) }));
|
||||
if (!tr.hasClass("non_cpm")) {
|
||||
if (tr.hasClass("sponsor") && !tr.hasClass("free")) {
|
||||
$(bid_td).append($(free).addClass("free")
|
||||
.click(function() { free_campaign(tr) }))
|
||||
}
|
||||
else if (!tr.hasClass("paid")) {
|
||||
$(bid_td).prepend($(pay).addClass("pay fancybutton")
|
||||
.click(function() { pay_campaign(tr) }));
|
||||
} else if (tr.hasClass("free")) {
|
||||
$(bid_td).addClass("free paid")
|
||||
.prepend("<span class='info'>freebie</span>");
|
||||
} else {
|
||||
(bid_td).addClass("paid")
|
||||
.prepend($(repay).addClass("pay fancybutton")
|
||||
.click(function() { pay_campaign(tr) }));
|
||||
}
|
||||
}
|
||||
var e = $(edit).addClass("edit fancybutton")
|
||||
.click(function() { edit_campaign(tr); });
|
||||
@@ -451,7 +522,9 @@ $.set_up_campaigns = function() {
|
||||
if (tr.hasClass("complete")) {
|
||||
$(td).append("<span class='info'>complete</span>");
|
||||
}
|
||||
$(bid_td).addClass("paid")
|
||||
if (!tr.hasClass("non_cpm")) {
|
||||
$(bid_td).addClass("paid")
|
||||
}
|
||||
/* sponsors can always edit */
|
||||
if (tr.hasClass("sponsor")) {
|
||||
var e = $(edit).addClass("edit fancybutton")
|
||||
@@ -461,6 +534,9 @@ $.set_up_campaigns = function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!r.sponsored.isSponsor) {
|
||||
$('.existing-campaigns tr td:nth-child(4), .existing-campaigns tr th:nth-child(4)').hide()
|
||||
}
|
||||
return $;
|
||||
|
||||
}
|
||||
@@ -543,6 +619,10 @@ function edit_campaign(elem) {
|
||||
i = '*[name="' + i + '"]';
|
||||
c.find(i).val(data_tr.find(i).val());
|
||||
});
|
||||
var priorities = c.find('*[name="priority"]'),
|
||||
campPriority = data_tr.find('*[name="priority"]').val()
|
||||
priorities.filter('*[value="' + campPriority + '"]')
|
||||
.prop("checked", "checked")
|
||||
/* check if targeting is turned on */
|
||||
var targeting = data_tr
|
||||
.find('*[name="targeting"]').val();
|
||||
@@ -598,6 +678,7 @@ function create_campaign() {
|
||||
.find('input[name="campaign_id36"]').val('').end()
|
||||
.find('input[name="sr"]').val('').prop("disabled", "disabled").end()
|
||||
.find('input[name="targeting"][value="none"]').prop("checked", "checked").end()
|
||||
.find('input[name="priority"][default="default"]').prop("checked", "checked").end()
|
||||
.find(".targeting").hide().end()
|
||||
.find('input[name="cpm"]').val(base_cpm).end()
|
||||
.fadeIn();
|
||||
|
||||
@@ -42,7 +42,7 @@ ${unsafe(js.use('sponsored'))}
|
||||
|
||||
<%def name="javascript_setup()">
|
||||
<script type="text/javascript">
|
||||
r.sponsored.init();
|
||||
r.sponsored.init(${unsafe(simplejson.dumps(c.user_is_sponsor))});
|
||||
r.sponsored.setup(${unsafe(simplejson.dumps(thing.inventory))})
|
||||
</script>
|
||||
</%def>
|
||||
@@ -344,6 +344,29 @@ ${self.javascript_setup()}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="${'display:none' if not c.user_is_sponsor else ''}">
|
||||
<th>${_("priority")}</th>
|
||||
<td class="prefright">
|
||||
%for value, text, description, default, inventory_override, cpm in thing.priorities:
|
||||
<label>
|
||||
<input id="${value}" class="nomargin"
|
||||
type="radio" value="${value}" name="priority"
|
||||
onclick="r.sponsored.priority_changed()"
|
||||
${"checked='checked'" if default else ""}
|
||||
${"default='default'" if default else ""}
|
||||
${"override='override'" if inventory_override else ""}
|
||||
${"cpm='cpm'" if cpm else ""}>
|
||||
%if description:
|
||||
<span>${"%s (%s)" % (text, description)}</span>
|
||||
%else:
|
||||
<span>${text}</span>
|
||||
%endif
|
||||
</label>
|
||||
<br />
|
||||
%endfor
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>${_("budget")}</th>
|
||||
<td class="prefright">
|
||||
@@ -469,6 +492,7 @@ ${self.javascript_setup()}
|
||||
<th title="${start_title}">${_("start")}</th>
|
||||
<th title="${end_title}">${_("end")}</th>
|
||||
<th>${_("duration")}</th>
|
||||
<th>${_("priority")}</th>
|
||||
<th>${_("total budget")}</th>
|
||||
<th>${_("spent")}</th>
|
||||
<th title="${targeting_title}">${_("targeting")}</th>
|
||||
@@ -486,7 +510,8 @@ ${self.javascript_setup()}
|
||||
%for rc in sorted(thing.campaigns, key=lambda rc: rc.start_date):
|
||||
$.new_campaign(${unsafe(','.join(simplejson.dumps(attr) for attr in
|
||||
[rc.campaign_id36, rc.start_date, rc.end_date, rc.duration,
|
||||
rc.bid, rc.spent, rc.cpm, rc.sr, rc.status]))});
|
||||
rc.bid, rc.spent, rc.cpm, rc.sr, rc.priority_name,
|
||||
rc.inventory_override, rc.status]))});
|
||||
%endfor
|
||||
$.set_up_campaigns();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user