def terminate_campaign(link, campaign): if not is_live_promo(link, campaign): return now = promo_datetime_now() original_end = campaign.end_date dates = [campaign.start_date, now] # NOTE: this will delete PromotionWeights after and including now.date() edit_campaign( link=link, campaign=campaign, dates=dates, target=campaign.target, frequency_cap=campaign.frequency_cap, priority=campaign.priority, location=campaign.location, total_budget_pennies=campaign.total_budget_pennies, cost_basis=campaign.cost_basis, bid_pennies=campaign.bid_pennies, ) campaigns = list(PromoCampaign._by_link(link._id)) is_live = any(is_live_promo(link, camp) for camp in campaigns if camp._id != campaign._id) if not is_live: update_promote_status(link, PROMOTE_STATUS.finished) all_live_promo_srnames(_update=True) msg = 'terminated campaign %s (original end %s)' % (campaign._id, original_end.date()) PromotionLog.add(link, msg)
def _use_adserver_reporting(thing): if not feature.is_enabled("adserver_reporting"): return False if not g.adserver_reporting_cutoff: return False try: cutoff = parse_date(g.adserver_reporting_cutoff) except ValueError: return False if isinstance(thing, PromoCampaign): link = Link._byID(thing.link_id) else: link = thing campaigns = list(PromoCampaign._by_link(link._id)) # No campaigns, so nothing to report. Show the new # view anyway. if not campaigns: return True end_date = max(campaign.end_date for campaign in campaigns) end_date = end_date.replace(tzinfo=g.tz) cutoff = cutoff.replace(tzinfo=g.tz) if end_date < cutoff: return False return not feature.is_enabled("legacy_ad_reporting")
def accept_promotion(link): was_edited_live = is_edited_live(link) update_promote_status(link, PROMOTE_STATUS.accepted) if link._spam: link._spam = False link._commit() if not was_edited_live: emailer.accept_promo(link) # if the link has campaigns running now charge them and promote the link now = promo_datetime_now() campaigns = list(PromoCampaign._by_link(link._id)) is_live = False for camp in campaigns: if is_accepted_promo(now, link, camp): # if link was edited live, do not check against Authorize.net if not was_edited_live: charge_campaign(link, camp) if charged_or_not_needed(camp): promote_link(link, camp) is_live = True if is_live: all_live_promo_srnames(_update=True)
def edit_promotion(link): if (not promote.is_external(link) and not list(PromoCampaign._by_link(link._id))): g.log.debug("no campaigns for link, skipping %s" % link._id) return update_adzerk(link)
def get_total_run(thing): """Return the total time span this link or campaign will run. Starts at the start date of the earliest campaign and goes to the end date of the latest campaign. """ if isinstance(thing, Link): campaigns = PromoCampaign._by_link(thing._id) elif isinstance(thing, PromoCampaign): campaigns = [thing] earliest = None latest = None for campaign in campaigns: if not charged_or_not_needed(campaign): continue if not earliest or campaign.start_date < earliest: earliest = campaign.start_date if not latest or campaign.end_date > latest: latest = campaign.end_date # a manually launched promo (e.g., sr discovery) might not have campaigns. if not earliest or not latest: latest = datetime.utcnow() earliest = latest - timedelta(days=30) # last month # ugh this stuff is a mess. they're stored as "UTC" but actually mean UTC-5. earliest = earliest.replace(tzinfo=g.tz) - timezone_offset latest = latest.replace(tzinfo=g.tz) - timezone_offset return earliest, latest
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) promotion_weights = PromotionWeights.get_campaigns(now) live_campaigns = {pw.promo_idx for pw in promotion_weights if pw.thing_name == link._fullname} if live_campaigns: campaigns = PromoCampaign._byID(live_campaigns, data=True, return_dict=False) PromotionLog.add(link, "has live campaigns, forcing live") charge_pending(0) # campaign must be charged before it will go live for campaign in campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [ camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged()) ] for campaign in charged_campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def make_campaign_table(self): campaigns = PromoCampaign._by_link(self.thing._id) total_budget = 0 total_spent = 0 total_paid_impressions = 0 total_impressions = 0 total_clicks = 0 self.campaign_table = [] for camp in campaigns: if not is_launched_campaign(camp): continue is_live = camp.is_live_now() self.has_early_campaign |= is_early_campaign(camp) self.has_live_campaign |= is_live history = get_billable_traffic(camp) impressions, clicks = 0, 0 for date, (imp, click) in history: impressions += imp clicks += click start = to_date(camp.start_date).strftime('%Y-%m-%d') end = to_date(camp.end_date).strftime('%Y-%m-%d') target = camp.target.pretty_name location = camp.location_str spent = promote.get_spent_amount(camp) is_active = self.campaign and self.campaign._id36 == camp._id36 url = '/traffic/%s/%s' % (self.thing._id36, camp._id36) is_total = False row = self.make_campaign_table_row(camp._id36, start, end, target, location, camp.bid, spent, camp.impressions, impressions, clicks, is_live, is_active, url, is_total) self.campaign_table.append(row) total_budget += camp.bid total_spent += spent total_paid_impressions += camp.impressions total_impressions += impressions total_clicks += clicks # total row start = '---' end = '---' target = '---' location = '---' is_live = False is_active = not self.campaign url = '/traffic/%s' % self.thing._id36 is_total = True row = self.make_campaign_table_row(_('total'), start, end, target, location, total_budget, total_spent, total_paid_impressions, total_impressions, total_clicks, is_live, is_active, url, is_total) self.campaign_table.append(row)
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ PromotionLog.add(link, 'status update: accepted') # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) if link._fullname in set(l.thing_name for l in PromotionWeights.get_campaigns(now)): PromotionLog.add(link, 'Marked promotion for acceptance') charge_pending(0) # campaign must be charged before it will go live queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged())] for campaign in charged_campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def get_total_run(link): """Return the total time span this promotion has run for. Starts at the start date of the earliest campaign and goes to the end date of the latest campaign. """ campaigns = PromoCampaign._by_link(link._id) earliest = None latest = None for campaign in campaigns: if not campaign.trans_id: continue if not earliest or campaign.start_date < earliest: earliest = campaign.start_date if not latest or campaign.end_date > latest: latest = campaign.end_date # a manually launched promo (e.g., sr discovery) might not have campaigns. if not earliest or not latest: latest = datetime.utcnow() earliest = latest - timedelta(days=30) # last month # ugh this stuff is a mess. they're stored as "UTC" but actually mean UTC-5. earliest = earliest.replace(tzinfo=None) - timezone_offset latest = latest.replace(tzinfo=None) - timezone_offset return earliest, latest
def __init__(self, link): self.thing = link self.edit_url = promote.promo_edit_url(link) self.is_preliminary = False campaigns = PromoCampaign._by_link(link._id) camps = {} fullnames = [] for campaign in campaigns: campaign.imps = 0 campaign.clicks = 0 self.is_preliminary |= _is_promo_preliminary(campaign.end_date) camps[campaign._fullname] = campaign fullnames.append(campaign._fullname) click_data = traffic.TargetedClickthroughsByCodename.total_by_codename( fullnames) for fullname, clicks in click_data: camps[fullname].clicks = clicks imp_data = traffic.TargetedImpressionsByCodename.total_by_codename( fullnames) for fullname, imps in imp_data: camps[fullname].imps = imps self.campaigns = camps.values() self.total_clicks = self.total_imps = self.total_spend = 0 for camp in self.campaigns: self.total_clicks += camp.clicks self.total_imps += camp.imps self.total_spend += camp.bid camp.ctr = _clickthrough_rate(camp.imps, camp.clicks) camp.cpc = cost_per_click(camp.bid, camp.clicks) camp.cpm = cost_per_mille(camp.bid, camp.imps) self.total_ctr = _clickthrough_rate(self.total_imps, self.total_clicks) self.total_cpc = cost_per_click(self.total_spend, self.total_clicks) self.total_cpm = cost_per_mille(self.total_spend, self.total_imps) Templated.__init__(self)
def get_total_budget(cls, thing): if isinstance(thing, Link): campaigns = PromoCampaign._by_link(thing._id) else: campaigns = [thing] total_budget_pennies = sum(map(lambda camp: camp.total_budget_pennies, campaigns)) return total_budget_pennies / 100.
def keep(item): if self.sort == "future_promos": # this sort is used to review links that need to be approved # skip links that don't have any paid campaigns campaigns = list(PromoCampaign._by_link(item._id)) if not any(promote.authed_or_not_needed(camp) for camp in campaigns): return False if item.promoted and not item._deleted: return True else: return False
def upsert_promotion(link): queue.push("upsert_promotion", { "link": link._fullname, }) action = ("activate" if promote.is_accepted(link) and not link._deleted else "deactivate") campaigns = list(PromoCampaign._by_link(link._id)) if not campaigns: return queue.push(action, { "campaigns": ",".join([campaign._fullname for campaign in campaigns]) })
def keep(item): if self.sort == "future_promos": # this sort is used to review links that need to be approved # skip links that don't have any paid campaigns campaigns = list(PromoCampaign._by_link(item._id)) if not any( promote.authed_or_not_needed(camp) for camp in campaigns): return False if item.promoted and not item._deleted: return True else: return False
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) promotion_weights = PromotionWeights.get_campaigns(now) live_campaigns = { pw.promo_idx for pw in promotion_weights if pw.thing_name == link._fullname } if live_campaigns: campaigns = PromoCampaign._byID(live_campaigns, data=True, return_dict=False) PromotionLog.add(link, 'has live campaigns, forcing live') charge_pending(0) # campaign must be charged before it will go live for campaign in campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [ camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now ] transactions = get_transactions(link, future_campaigns) charged_campaigns = [ camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged()) ] for campaign in charged_campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def listing(self): """For sponsors, update wrapped links to include their campaigns.""" pane = super(self.__class__, self).listing() if c.user_is_sponsor: link_ids = {item._id for item in pane.things} campaigns = PromoCampaign._by_link(link_ids) campaigns_by_link = defaultdict(list) for camp in campaigns: campaigns_by_link[camp.link_id].append(camp) for item in pane.things: campaigns = campaigns_by_link[item._id] item.campaigns = RenderableCampaign.from_campaigns( item, campaigns, full_details=False) item.cachable = False item.show_campaign_summary = True return pane
def accept_promotion(link): update_promote_status(link, PROMOTE_STATUS.accepted) if link._spam: link._spam = False link._commit() emailer.accept_promo(link) # if the link has campaigns running now charge them and promote the link now = promo_datetime_now() campaigns = list(PromoCampaign._by_link(link._id)) is_live = False for camp in campaigns: if is_accepted_promo(now, link, camp): charge_campaign(link, camp) if charged_or_not_needed(camp): promote_link(link, camp) is_live = True if is_live: all_live_promo_srnames(_update=True)
def terminate_campaign(link, campaign): if not is_live_promo(link, campaign): return now = promo_datetime_now() original_end = campaign.end_date dates = [campaign.start_date, now] # NOTE: this will delete PromotionWeights after and including now.date() edit_campaign(link, campaign, dates, campaign.bid, campaign.cpm, campaign.target, campaign.priority, campaign.location) campaigns = list(PromoCampaign._by_link(link._id)) is_live = any(is_live_promo(link, camp) for camp in campaigns if camp._id != campaign._id) if not is_live: update_promote_status(link, PROMOTE_STATUS.finished) all_live_promo_srnames(_update=True) msg = 'terminated campaign %s (original end %s)' % (campaign._id, original_end.date()) PromotionLog.add(link, msg)
def get_total_run(thing): """Return the total time span this link or campaign will run. Starts at the start date of the earliest campaign and goes to the end date of the latest campaign. """ campaigns = [] if isinstance(thing, Link): campaigns = PromoCampaign._by_link(thing._id) elif isinstance(thing, PromoCampaign): campaigns = [thing] else: campaigns = [] earliest = None latest = None for campaign in campaigns: if not charged_or_not_needed(campaign): continue if not earliest or campaign.start_date < earliest: earliest = campaign.start_date if not latest or campaign.end_date > latest: latest = campaign.end_date # a manually launched promo (e.g., sr discovery) might not have campaigns. if not earliest or not latest: latest = datetime.datetime.utcnow() earliest = latest - datetime.timedelta(days=30) # last month # ugh this stuff is a mess. they're stored as "UTC" but actually mean UTC-5. earliest = earliest.replace(tzinfo=g.tz) - timezone_offset latest = latest.replace(tzinfo=g.tz) - timezone_offset return earliest, latest
def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid, target, priority, location): if not link: return if not target: # run form.has_errors to populate the errors in the response form.has_errors('sr', errors.SUBREDDIT_NOEXIST, errors.SUBREDDIT_NOTALLOWED, errors.SUBREDDIT_REQUIRED) form.has_errors('collection', errors.COLLECTION_NOEXIST) form.has_errors('targeting', errors.INVALID_TARGET) return start, end = dates or (None, None) if not allowed_location_and_target(location, target): return abort(403, 'forbidden') cpm = PromotionPrices.get_price(target, location) if (form.has_errors('startdate', errors.BAD_DATE, errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE) or form.has_errors('enddate', errors.BAD_DATE, errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE, errors.BAD_DATE_RANGE)): return # check that start is not so late that authorization hold will expire if not c.user_is_sponsor: max_start = promote.get_max_startdate() if start > max_start: c.errors.add( errors.DATE_TOO_LATE, msg_params={'day': max_start.strftime("%m/%d/%Y")}, field='startdate') form.has_errors('startdate', errors.DATE_TOO_LATE) return # Limit the number of PromoCampaigns a Link can have # Note that the front end should prevent the user from getting # this far existing_campaigns = list(PromoCampaign._by_link(link._id)) if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK: c.errors.add(errors.TOO_MANY_CAMPAIGNS, msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK}, field='title') form.has_errors('title', errors.TOO_MANY_CAMPAIGNS) return campaign = None if campaign_id36: try: campaign = PromoCampaign._byID36(campaign_id36) except NotFound: pass if campaign and link._id != campaign.link_id: return abort(404, 'not found') if priority.cpm: min_bid = 0 if c.user_is_sponsor else g.min_promote_bid max_bid = None if c.user_is_sponsor else g.max_promote_bid if bid is None or bid < min_bid or (max_bid and bid > max_bid): c.errors.add(errors.BAD_BID, field='bid', msg_params={ 'min': min_bid, 'max': max_bid or g.max_promote_bid }) 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 promote.is_live_promo(link, campaign) and not campaign.is_freebie()): c.errors.add(errors.BID_LIVE, field='bid') form.has_errors('bid', errors.BID_LIVE) return else: bid = 0. # Set bid to 0 as dummy value is_frontpage = (not target.is_collection and target.subreddit_name == Frontpage.name) if not target.is_collection and not is_frontpage: # targeted to a single subreddit, check roadblock sr = target.subreddits_slow[0] roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end) if roadblock and not c.user_is_sponsor: msg_params = { "start": roadblock[0].strftime('%m/%d/%Y'), "end": roadblock[1].strftime('%m/%d/%Y') } c.errors.add(errors.OVERSOLD, field='sr', msg_params=msg_params) form.has_errors('sr', errors.OVERSOLD) return # Check inventory campaign = campaign if campaign_id36 else None if not priority.inventory_override: oversold = has_oversold_error(form, campaign, start, end, bid, cpm, target, location) if oversold: return if campaign: promote.edit_campaign(link, campaign, dates, bid, cpm, target, priority, location) else: campaign = promote.new_campaign(link, dates, bid, cpm, target, priority, location) rc = RenderableCampaign.from_campaigns(link, campaign) jquery.update_campaign(campaign._fullname, rc.render_html())
def _handle_generate_daily_link_report(link_id): now = datetime.utcnow() link = Link._byID(link_id, data=True) campaigns = list(PromoCampaign._by_link(link._id)) if not campaigns: return link_start = min([promo.start_date for promo in campaigns]) link_end = max([promo.end_date for promo in campaigns]) now = now.replace(tzinfo=pytz.utc) link_start = link_start.replace(tzinfo=pytz.utc) link_end = link_end.replace(tzinfo=pytz.utc) # if data has already been processed then there's no need # to redo it. use the last time the report was run as a # starting point, but subtract 24hrs since initial numbers # are preliminary. if hasattr(link, "last_daily_report_run"): start = max([ link.last_daily_report_run - timedelta(hours=24), link_start, ]) # in cases where we may be running a report well after a link # has completed ensure we always use the actual start. if start > link_end: start = link_start else: start = link_start end = min([now, link_end]) g.log.info("generating report for link %s" % link._fullname) report_id = report.queue_report( start=start, end=end, groups=["optionId", "day"], parameters=[{ "campaignId": link.external_campaign_id, }], ) g.log.info("processing report for link (%s/%s)" % (link._fullname, report_id)) try: _process_daily_link_report( link=link, report_id=report_id, queued_date=now, ) g.log.info("successfully processed report for link (%s/%s)" % (link._fullname, report_id)) except report.ReportFailedException as e: g.log.error(e) # retry if report failed _generate_link_report(link)
def _process_daily_link_report(link, report_id, queued_date): """ Processes report grouped by day and flight. Exponentially backs off on retries, throws on timeout. """ attempt = 1 while True: try: report_result = report.fetch_report(report_id) break except report.ReportPendingException as e: timeout = (datetime.utcnow().replace(tzinfo=pytz.utc) - timedelta(seconds=g.az_reporting_timeout)) if queued_date < timeout: raise report.ReportFailedException( "link report timed out (%s/%s)" % (link._fullname, report_id)) else: sleep_time = math.pow(RETRY_SLEEP_SECONDS, attempt) attempt = attempt + 1 g.log.warning( "link report still pending, retrying in %d seconds (%s/%s)" % (RETRY_SLEEP_SECONDS, link._fullname, report_id)) time.sleep(sleep_time) g.log.debug(report_result) campaigns_by_fullname = { campaign._fullname: campaign for campaign in PromoCampaign._by_link(link._id) } # report is by date, by flight. each record is a day # and each detail is a flight for that day. for record in report_result.get("Records", []): impressions, clicks, spent = _get_usage(record) date = _get_date(record) _insert_daily_link_reporting( codename=link._fullname, date=date, impressions=impressions, clicks=clicks, spent_pennies=spent * 100., ) for detail in record.get("Details", []): campaign_fullname = _get_fullname(PromoCampaign, detail) if not campaign_fullname: g.log.error("invalid fullname for campaign (%s/%s)" % (campaign_fullname, flight_id)) continue campaign = campaigns_by_fullname.get(campaign_fullname) if not campaign: flight_id = _get_flight_id(detail) g.log.warning("no campaign for flight (%s/%s)" % (campaign_fullname, flight_id)) continue impressions, clicks, spent = _get_usage(detail) _insert_daily_campaign_reporting( codename=campaign._fullname, date=date, impressions=impressions, clicks=clicks, spent_pennies=spent * 100., subreddit=campaign.target_name, ) link.last_daily_report = report_id link.last_daily_report_run = queued_date link._commit()
def make_campaign_table(self): campaigns = PromoCampaign._by_link(self.thing._id) total_budget_dollars = 0. total_spent = 0 total_paid_impressions = 0 total_impressions = 0 total_clicks = 0 all_auction = True self.campaign_table = [] for camp in campaigns: if not is_launched_campaign(camp): continue is_live = camp.is_live_now() self.has_early_campaign |= is_early_campaign(camp) self.has_live_campaign |= is_live history = list(get_billable_traffic(camp)) if not history: impressions, clicks, spent = 0, 0, 0 elif self.use_adserver_reporting: impressions, clicks, spent = map( sum, zip(*[values for date, values in history])) else: impressions, clicks = map( sum, zip(*[values for date, values in history])) spent = promote.get_spent_amount(camp) start = to_date(camp.start_date).strftime('%Y-%m-%d') end = to_date(camp.end_date).strftime('%Y-%m-%d') target = camp.target.pretty_name location = camp.location_str is_active = self.campaign and self.campaign._id36 == camp._id36 url = '/traffic/%s/%s' % (self.thing._id36, camp._id36) is_total = False campaign_budget_dollars = camp.total_budget_dollars row = self.make_campaign_table_row( camp._id36, start=start, end=end, target=target, location=location, budget_dollars=campaign_budget_dollars, spent=spent, paid_impressions=camp.impressions, impressions=impressions, clicks=clicks, is_live=is_live, is_active=is_active, url=url, is_total=is_total, is_auction=camp.is_auction) self.campaign_table.append(row) total_budget_dollars += campaign_budget_dollars total_spent += spent total_paid_impressions += camp.impressions total_impressions += impressions total_clicks += clicks if not camp.is_auction: all_auction = False # total row start = '---' end = '---' target = '---' location = '---' is_live = False is_active = not self.campaign url = '/traffic/%s' % self.thing._id36 row = self.make_campaign_table_row( _('total'), start=start, end=end, target=target, location=location, budget_dollars=total_budget_dollars, spent=total_spent, paid_impressions=total_paid_impressions, impressions=total_impressions, clicks=total_clicks, is_live=is_live, is_active=is_active, url=url, is_total=True, is_auction=all_auction) self.campaign_table.append(row)
def make_campaign_table(self): campaigns = PromoCampaign._by_link(self.thing._id) total_budget_dollars = 0. total_spent = 0 total_paid_impressions = 0 total_impressions = 0 total_clicks = 0 self.campaign_table = [] for camp in campaigns: if not is_launched_campaign(camp): continue is_live = camp.is_live_now() self.has_early_campaign |= is_early_campaign(camp) self.has_live_campaign |= is_live history = get_billable_traffic(camp) impressions, clicks = 0, 0 for date, (imp, click) in history: impressions += imp clicks += click start = to_date(camp.start_date).strftime('%Y-%m-%d') end = to_date(camp.end_date).strftime('%Y-%m-%d') target = camp.target.pretty_name location = camp.location_str spent = promote.get_spent_amount(camp) is_active = self.campaign and self.campaign._id36 == camp._id36 url = '/traffic/%s/%s' % (self.thing._id36, camp._id36) is_total = False campaign_budget_dollars = camp.total_budget_dollars row = self.make_campaign_table_row( camp._id36, start=start, end=end, target=target, location=location, budget_dollars=campaign_budget_dollars, spent=spent, paid_impressions=camp.impressions, impressions=impressions, clicks=clicks, is_live=is_live, is_active=is_active, url=url, is_total=is_total) self.campaign_table.append(row) total_budget_dollars += campaign_budget_dollars total_spent += spent total_paid_impressions += camp.impressions total_impressions += impressions total_clicks += clicks # total row start = '---' end = '---' target = '---' location = '---' is_live = False is_active = not self.campaign url = '/traffic/%s' % self.thing._id36 is_total = True row = self.make_campaign_table_row( _('total'), start=start, end=end, target=target, location=location, budget_dollars=total_budget_dollars, spent=total_spent, paid_impressions=total_paid_impressions, impressions=total_impressions, clicks=total_clicks, is_live=is_live, is_active=is_active, url=url, is_total=is_total) self.campaign_table.append(row)
def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid, sr, targeting): if not link: return start, end = dates or (None, None) author = Account._byID(link.author_id, data=True) cpm = author.cpm_selfserve_pennies if (start and end and not promote.is_accepted(link) and not c.user_is_sponsor): # if the ad is not approved already, ensure the start date # is at least 2 days in the future start = start.date() end = end.date() now = promote.promo_datetime_now() future = make_offset_date(now, g.min_promote_future, business_days=True) if start < future.date(): c.errors.add(errors.BAD_FUTURE_DATE, msg_params=dict(day=g.min_promote_future), field="startdate") if (form.has_errors('startdate', errors.BAD_DATE, errors.BAD_FUTURE_DATE) or form.has_errors('enddate', errors.BAD_DATE, errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE)): return # Limit the number of PromoCampaigns a Link can have # Note that the front end should prevent the user from getting # this far existing_campaigns = list(PromoCampaign._by_link(link._id)) if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK: c.errors.add(errors.TOO_MANY_CAMPAIGNS, msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK}, field='title') form.has_errors('title', errors.TOO_MANY_CAMPAIGNS) return if form.has_errors('bid', errors.BAD_BID): return 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 targeting == 'one': if form.has_errors('sr', errors.SUBREDDIT_NOEXIST, errors.SUBREDDIT_NOTALLOWED, errors.SUBREDDIT_REQUIRED): # checking to get the error set in the form, but we can't # check for rate-limiting if there's no subreddit return roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end) if roadblock and not c.user_is_sponsor: msg_params = { "start": roadblock[0].strftime('%m/%d/%Y'), "end": roadblock[1].strftime('%m/%d/%Y') } c.errors.add(errors.OVERSOLD, field='sr', msg_params=msg_params) form.has_errors('sr', errors.OVERSOLD) return elif targeting == 'none': 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): return if campaign_id36 is not None: campaign = PromoCampaign._byID36(campaign_id36) promote.edit_campaign(link, campaign, dates, bid, cpm, sr) 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) else: campaign = promote.new_campaign(link, dates, bid, cpm, sr) 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)
def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid, sr, targeting, priority, location): if not link: return start, end = dates or (None, None) author = Account._byID(link.author_id, data=True) cpm = author.cpm_selfserve_pennies if location: cpm += g.cpm_selfserve_geotarget.pennies if (form.has_errors('startdate', errors.BAD_DATE, errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE) or form.has_errors('enddate', errors.BAD_DATE, errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE, errors.BAD_DATE_RANGE)): return # Limit the number of PromoCampaigns a Link can have # Note that the front end should prevent the user from getting # this far existing_campaigns = list(PromoCampaign._by_link(link._id)) if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK: c.errors.add(errors.TOO_MANY_CAMPAIGNS, msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK}, field='title') form.has_errors('title', errors.TOO_MANY_CAMPAIGNS) return campaign = None if campaign_id36: try: campaign = PromoCampaign._byID36(campaign_id36) except NotFound: pass 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 promote.is_live_promo(link, campaign) 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, errors.SUBREDDIT_NOTALLOWED, errors.SUBREDDIT_REQUIRED): # checking to get the error set in the form, but we can't # check for rate-limiting if there's no subreddit return roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end) if roadblock and not c.user_is_sponsor: msg_params = { "start": roadblock[0].strftime('%m/%d/%Y'), "end": roadblock[1].strftime('%m/%d/%Y') } c.errors.add(errors.OVERSOLD, field='sr', msg_params=msg_params) form.has_errors('sr', errors.OVERSOLD) return elif targeting == 'none': sr = None # Check inventory campaign = campaign if campaign_id36 else None if not priority.inventory_override: oversold = has_oversold_error(form, campaign, start, end, bid, cpm, sr, location) if oversold: return if campaign: promote.edit_campaign(link, campaign, dates, bid, cpm, sr, priority, location) else: campaign = promote.new_campaign(link, dates, bid, cpm, sr, priority, location) rc = RenderableCampaign.from_campaigns(link, campaign) jquery.update_campaign(campaign._fullname, rc.render_html())
def edit_promotion(link): if not list(PromoCampaign._by_link(link._id)): g.log.debug("no campaigns for link, skipping %s" % link._id) return update_adzerk(link)