Example #1
0
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 _handle_adzerk(msg):
     data = json.loads(msg.body)
     g.log.debug('data: %s' % data)
     action = data.get('action')
     if action == 'deactivate_link':
         link = Link._by_fullname(data['link'], data=True)
         _deactivate_link(link)
     elif action == 'deactivate_campaign':
         link = Link._by_fullname(data['link'], data=True)
         campaign = PromoCampaign._by_fullname(data['campaign'], data=True)
         _deactivate_campaign(link, campaign)
     elif action == 'update_adzerk':
         link = Link._by_fullname(data['link'], data=True)
         campaign = PromoCampaign._by_fullname(data['campaign'], data=True)
         _update_adzerk(link, campaign)
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 _handle_adzerk(msg):
        data = json.loads(msg.body)
        g.log.debug('data: %s' % data)

        action = data.get('action')

        if action == 'deactivate_orphaned_flight':
            _deactivate_orphaned_flight(data['flight'])
            return

        link = Link._by_fullname(data['link'], data=True)
        if data['campaign']:
            campaign = PromoCampaign._by_fullname(data['campaign'], data=True)
        else:
            campaign = None

        if action == 'update_adzerk':
            if 'triggered_by' in data and data['triggered_by'] is not None:
                triggered_by = Account._by_fullname(data['triggered_by'], data=True)
            else:
                triggered_by = None

            _update_adzerk(link, campaign, triggered_by)

        elif action == 'deactivate_overdelivered':
            _deactivate_overdelivered(link, campaign)
Example #5
0
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")
Example #6
0
 def query(self):
     if c.user_is_sponsor:
         if self.sort == "future_promos":
             return queries.get_all_unapproved_links()
         elif self.sort == "pending_promos":
             return queries.get_all_accepted_links()
         elif self.sort == "unpaid_promos":
             return queries.get_all_unpaid_links()
         elif self.sort == "rejected_promos":
             return queries.get_all_rejected_links()
         elif self.sort == "live_promos" and self.sr:
             return self.live_by_subreddit(self.sr)
         elif self.sort == 'live_promos':
             return queries.get_all_live_links()
         elif self.sort == 'underdelivered':
             q = queries.get_underdelivered_campaigns()
             campaigns = PromoCampaign._by_fullname(list(q), data=True,
                                                    return_dict=False)
             link_ids = [camp.link_id for camp in campaigns]
             return [Link._fullname_from_id36(to36(id)) for id in link_ids]
         elif self.sort == 'reported':
             return queries.get_reported_links(get_promote_srid())
         return queries.get_all_promoted_links()
     else:
         if self.sort == "future_promos":
             return queries.get_unapproved_links(c.user._id)
         elif self.sort == "pending_promos":
             return queries.get_accepted_links(c.user._id)
         elif self.sort == "unpaid_promos":
             return queries.get_unpaid_links(c.user._id)
         elif self.sort == "rejected_promos":
             return queries.get_rejected_links(c.user._id)
         elif self.sort == "live_promos":
             return queries.get_live_links(c.user._id)
         return queries.get_promoted_links(c.user._id)
Example #7
0
def scheduled_campaigns_by_link(l, date=None):
    # A promotion/campaign is scheduled/live if it's in
    # PromotionWeights.get_campaigns(now) and
    # charged_or_not_needed

    date = date or promo_datetime_now()

    if not is_accepted(l):
        return []

    scheduled = PromotionWeights.get_campaigns(date)
    campaigns = [c.promo_idx for c in scheduled if c.thing_name == l._fullname]

    # Check authorize
    accepted = []
    for campaign_id in campaigns:
        try:
            campaign = PromoCampaign._byID(campaign_id, data=True)
            if charged_or_not_needed(campaign):
                accepted.append(campaign_id)
        except NotFound:
            g.log.error("PromoCampaign %d scheduled to run on %s not found." %
                          (campaign_id, date.strftime("%Y-%m-%d")))

    return accepted
Example #8
0
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
Example #9
0
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)
Example #10
0
    def GET_report(self, start, end, link_text=None, owner=None):
        now = datetime.now(g.tz).replace(hour=0, minute=0, second=0,
                                         microsecond=0)
        end = end or now - timedelta(days=1)
        start = start or end - timedelta(days=7)

        links = []
        bad_links = []
        owner_name = owner.name if owner else ''

        if owner:
            promo_weights = PromotionWeights.get_campaigns(start, end,
                                                           author_id=owner._id)
            campaign_ids = [pw.promo_idx for pw in promo_weights]
            campaigns = PromoCampaign._byID(campaign_ids, data=True)
            link_ids = {camp.link_id for camp in campaigns.itervalues()}
            links.extend(Link._byID(link_ids, data=True, return_dict=False))

        if link_text is not None:
            id36s = link_text.replace(',', ' ').split()
            try:
                links_from_text = Link._byID36(id36s, data=True)
            except NotFound:
                links_from_text = {}

            bad_links = [id36 for id36 in id36s if id36 not in links_from_text]
            links.extend(links_from_text.values())

        content = PromoteReport(links, link_text, owner_name, bad_links, start,
                                end)
        if c.render_style == 'csv':
            return content.as_csv()
        else:
            return PromotePage(title=_("sponsored link report"),
                               content=content).render()
Example #11
0
    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)
Example #12
0
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)
Example #13
0
def get_promos(date, sr_names=None, link=None):
    campaign_ids = PromotionWeights.get_campaign_ids(date, sr_names=sr_names, link=link)
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)
    link_ids = {camp.link_id for camp in campaigns}
    links = Link._byID(link_ids, data=True)
    for camp in campaigns:
        yield camp, links[camp.link_id]
Example #14
0
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)
Example #15
0
 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)
Example #16
0
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
Example #17
0
def get_sold_pageviews(srs, start, end, ignore=None):
    srs, is_single = tup(srs, ret_is_single=True)
    sr_names = ['' if isinstance(sr, DefaultSR) else sr.name for sr in srs]
    dates = set(get_date_range(start, end))
    ignore = [] if ignore is None else ignore
    q = (PromotionWeights.query()
                .filter(PromotionWeights.sr_name.in_(sr_names))
                .filter(PromotionWeights.date.in_(dates)))
    campaign_ids = {pw.promo_idx for pw in q}
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)

    ret = {sr.name: dict.fromkeys(dates, 0) for sr in srs}
    for camp in campaigns:
        if camp.trans_id == NO_TRANSACTION:
            continue

        if ignore and camp._id in ignore:
            continue

        if camp.impressions <= 0:
            # pre-CPM campaign
            continue

        sr_name = camp.sr_name or DefaultSR.name
        daily_impressions = camp.impressions / camp.ndays
        camp_dates = set(get_date_range(camp.start_date, camp.end_date))
        for date in camp_dates.intersection(dates):
            ret[sr_name][date] += daily_impressions

    if is_single:
        return ret[srs[0].name]
    else:
        return ret
def _handle_generate_lifetime_campaign_report(campaign_id):
    now = datetime.utcnow()
    campaign = PromoCampaign._byID(campaign_id, data=True)
    start = campaign.start_date.replace(tzinfo=pytz.utc)
    end = campaign.end_date.replace(tzinfo=pytz.utc)
    now = now.replace(tzinfo=pytz.utc)

    end = min([now, end])

    g.log.info("generating report for campaign %s" % campaign._fullname)

    report_id = report.queue_report(
        start=start,
        end=end,
        parameters=[{
            "flightId": campaign.external_flight_id,
        }],
    )

    try:
        _process_lifetime_campaign_report(
            campaign=campaign,
            report_id=report_id,
            queued_date=now,
        )

        g.log.info("successfully processed report for campaign (%s/%s)" %
            (campaign._fullname, report_id))
    except report.ReportFailedException as e:
        g.log.error(e)
        # retry if report failed
        _generate_promo_report(campaign)
def _handle_generate_daily_link_reports(link_ids, campaign_ids):
    now = datetime.utcnow()
    links = Link._byID(link_ids, data=True, return_dict=False)
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)

    if not campaigns:
        return

    links_start, links_end = _get_campaigns_date_range(campaigns)
    now = now.replace(tzinfo=pytz.utc)
    links_start = links_start.replace(tzinfo=pytz.utc)
    links_end = links_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.
    last_run = min(getattr(l, "last_daily_report_run", links_start) for l in links)
    start = max(
        last_run - timedelta(hours=24),
        links_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 > links_end:
        start = links_start

    end = min([now, links_end])

    link_fullnames = ",".join([l._fullname for l in links])
    g.log.info("generating report for link %s (%s-%s)" % (
        link_fullnames, start.strftime('%Y-%m-%d'), end.strftime('%Y-%m-%d')))

    report_id = report.queue_report(
        start=start,
        end=end,
        groups=["optionId", "day"],
        parameters=[{
            "campaignId": l.external_campaign_id,
        } for l in links],
    )

    g.log.info("processing report for link (%s/%s)" %
        (link_fullnames, report_id))

    try:
        _process_daily_link_reports(
            links=links,
            report_id=report_id,
            queued_date=now,
        )

        g.log.info("successfully processed report for link (%s/%s)" %
            (link_fullnames, report_id))
    except report.ReportFailedException as e:
        g.log.error(e)
        # retry if report failed
        _generate_link_reports(links)
Example #20
0
 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
Example #21
0
    def _handle_deactivate(payload):
        campaign_ids = payload["campaigns"] and payload["campaigns"].split(",")

        if not campaign_ids:
            return

        campaigns = PromoCampaign._by_fullname(campaign_ids, data=True)
        lineitems_service.deactivate(campaign_ids)
Example #22
0
 def get_house_link_names(cls):
     now = promote.promo_datetime_now()
     pws = PromotionWeights.get_campaigns(now)
     campaign_ids = {pw.promo_idx for pw in pws}
     q = PromoCampaign._query(PromoCampaign.c._id.in_(campaign_ids),
                              PromoCampaign.c.priority_name == 'house',
                              data=True)
     return [Link._fullname_from_id36(to36(camp.link_id)) for camp in q]
Example #23
0
 def get_house_link_names(cls):
     now = promote.promo_datetime_now()
     campaign_ids = PromotionWeights.get_campaign_ids(now)
     q = PromoCampaign._query(PromoCampaign.c._id.in_(campaign_ids),
                              PromoCampaign.c.priority_name == 'house',
                              data=True)
     link_names = {Link._fullname_from_id36(to36(camp.link_id))
                   for camp in q}
     return sorted(link_names, reverse=True)
Example #24
0
    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.
Example #25
0
def new_campaign(link, dates, bid, sr):
    # empty string for sr_name means target to all
    sr_name = sr.name if sr else ""
    campaign = PromoCampaign._new(link, sr_name, bid, dates[0], dates[1])
    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, True)
    if getattr(author, "complimentary_promos", False):
        free_campaign(link, campaign, c.user)
    return campaign
Example #26
0
def _handle_upsert_campaign(payload):
    link = Link._by_fullname(payload["link"], data=True)
    campaign = PromoCampaign._by_fullname(payload["campaign"], data=True)
    owner = Account._byID(campaign.owner_id)

    lineitem = lineitems_service.upsert_lineitem(owner, campaign)
    creative = creatives_service.get_creative(link)

    lineitems_service.associate_with_creative(
        lineitem=lineitem, creative=creative)
Example #27
0
def update_served(items):
    for item in items:
        if not item.promoted:
            continue

        campaign = PromoCampaign._by_fullname(item.campaign)

        if not campaign.has_served:
            campaign.has_served = True
            campaign._commit()
Example #28
0
def filter_campaigns(date, fullnames):
    campaigns = PromoCampaign._by_fullname(fullnames, data=True, return_dict=False)

    # filter out campaigns that shouldn't be live
    pc_date = datetime.datetime(date.year, date.month, date.day, 0, 0, tzinfo=g.tz)

    campaigns = [camp for camp in campaigns if camp.start_date <= pc_date <= camp.end_date]

    # check for links with targeted campaigns - we can't handle them now
    has_targeted = [camp.link_id for camp in campaigns if camp.sr_name != ""]
    return [camp for camp in campaigns if camp.link_id not in has_targeted]
Example #29
0
        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
Example #30
0
def new_campaign(link, dates, bid, cpm, target, priority, location, platform, mobile_os):
    campaign = PromoCampaign.create(link, target, bid, cpm, dates[0], dates[1], priority, location, platform, mobile_os)
    PromotionWeights.add(link, campaign)
    PromotionLog.add(link, "campaign %s created" % campaign._id)

    if campaign.priority.cpm:
        author = Account._byID(link.author_id, data=True)
        if getattr(author, "complimentary_promos", False):
            free_campaign(link, campaign, c.user)

    hooks.get_hook("promote.new_campaign").call(link=link, campaign=campaign)
    return campaign
Example #31
0
    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
Example #32
0
def new_campaign(link, dates, target, frequency_cap, priority, location,
                 platform, mobile_os, ios_devices, ios_version_range,
                 android_devices, android_version_range, total_budget_pennies,
                 cost_basis, bid_pennies):
    campaign = PromoCampaign.create(
        link, target, dates[0], dates[1], frequency_cap, priority, location,
        platform, mobile_os, ios_devices, ios_version_range, android_devices,
        android_version_range, total_budget_pennies, cost_basis, bid_pennies)
    PromotionWeights.add(link, campaign)
    PromotionLog.add(link, 'campaign %s created' % campaign._id)

    if not campaign.is_house:
        author = Account._byID(link.author_id, data=True)
        if getattr(author, "complimentary_promos", False):
            free_campaign(link, campaign, c.user)

    hooks.get_hook('promote.new_campaign').call(link=link, campaign=campaign)
    return campaign
Example #33
0
def get_campaigns_by_date(srs, start, end, ignore=None):
    srs, is_single = tup(srs, ret_is_single=True)
    sr_names = ['' if isinstance(sr, DefaultSR) else sr.name for sr in srs]
    dates = set(get_date_range(start, end))
    q = (PromotionWeights.query()
                .filter(PromotionWeights.sr_name.in_(sr_names))
                .filter(PromotionWeights.date.in_(dates)))

    if ignore:
        q = q.filter(PromotionWeights.promo_idx != ignore._id)

    campaign_ids = {pw.promo_idx for pw in q}
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)
    transaction_ids = {camp.trans_id for camp in campaigns
                                     if camp.trans_id != NO_TRANSACTION}

    if transaction_ids:
        transactions = Bid.query().filter(Bid.transaction.in_(transaction_ids))
        transaction_by_id = {bid.transaction: bid for bid in transactions}
    else:
        transaction_by_id = {}

    ret = {sr.name: defaultdict(list) for sr in srs}
    for camp in campaigns:
        if camp.trans_id == NO_TRANSACTION:
            continue

        if camp.impressions <= 0:
            # pre-CPM campaign
            continue

        transaction = transaction_by_id[camp.trans_id]
        if not (transaction.is_auth() or transaction.is_charged()):
            continue

        sr_name = camp.sr_name or DefaultSR.name
        camp_dates = set(get_date_range(camp.start_date, camp.end_date))
        for date in camp_dates.intersection(dates):
            ret[sr_name][date].append(camp)

    if is_single:
        return ret[srs[0].name]
    else:
        return ret
Example #34
0
def get_campaigns_by_date(srs, start, end, ignore=None):
    srs = tup(srs)
    sr_names = [sr.name for sr in srs]
    campaign_ids = PromotionWeights.get_campaign_ids(start,
                                                     end=end,
                                                     sr_names=sr_names)
    if ignore:
        campaign_ids.discard(ignore._id)
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)

    # filter out deleted campaigns that didn't have their PromotionWeights
    # deleted
    campaigns = filter(lambda camp: not camp._deleted, campaigns)

    transaction_ids = {
        camp.trans_id
        for camp in campaigns if camp.trans_id != NO_TRANSACTION
    }

    if transaction_ids:
        transactions = Bid.query().filter(Bid.transaction.in_(transaction_ids))
        transaction_by_id = {bid.transaction: bid for bid in transactions}
    else:
        transaction_by_id = {}

    dates = set(get_date_range(start, end))
    ret = {date: set() for date in dates}
    for camp in campaigns:
        if camp.trans_id == NO_TRANSACTION:
            continue

        if camp.impressions <= 0:
            # pre-CPM campaign
            continue

        transaction = transaction_by_id[camp.trans_id]
        if not (transaction.is_auth() or transaction.is_charged()):
            continue

        camp_dates = set(get_date_range(camp.start_date, camp.end_date))
        for date in camp_dates.intersection(dates):
            ret[date].add(camp)
    return ret
Example #35
0
def get_sold_pageviews(srs, start, end, ignore=None):
    srs, is_single = tup(srs, ret_is_single=True)
    sr_names = ['' if isinstance(sr, DefaultSR) else sr.name for sr in srs]
    dates = set(get_date_range(start, end))
    ignore = [] if ignore is None else ignore
    q = (PromotionWeights.query().filter(
        PromotionWeights.sr_name.in_(sr_names)).filter(
            PromotionWeights.date.in_(dates)))
    campaign_ids = {pw.promo_idx for pw in q}
    campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=False)
    transaction_ids = {
        camp.trans_id
        for camp in campaigns if camp.trans_id != NO_TRANSACTION
    }
    transactions = Bid.query().filter(Bid.transaction.in_(transaction_ids))
    transaction_by_id = {bid.transaction: bid for bid in transactions}

    ret = {sr.name: dict.fromkeys(dates, 0) for sr in srs}
    for camp in campaigns:
        if camp.trans_id == NO_TRANSACTION:
            continue

        if ignore and camp._id in ignore:
            continue

        if camp.impressions <= 0:
            # pre-CPM campaign
            continue

        transaction = transaction_by_id[camp.trans_id]
        if not (transaction.is_auth() or transaction.is_charged()):
            continue

        sr_name = camp.sr_name or DefaultSR.name
        daily_impressions = camp.impressions / camp.ndays
        camp_dates = set(get_date_range(camp.start_date, camp.end_date))
        for date in camp_dates.intersection(dates):
            ret[sr_name][date] += daily_impressions

    if is_single:
        return ret[srs[0].name]
    else:
        return ret
Example #36
0
def get_scheduled(date, sr_name=''):
    campaign_ids = PromotionWeights.get_campaign_ids(date, sr_names=[sr_name])
    campaigns = PromoCampaign._byID(campaign_ids, return_dict=False, data=True)
    links = Link._by_fullname({camp.link_id
                               for camp in campaigns},
                              return_dict=False,
                              data=True)
    links = {l._id: l for l in links}
    kept = []
    for camp in campaigns:
        if camp.trans_id == 0:
            continue

        link = links[camp.link_id]
        if link._spam or not promote.is_accepted(link):
            continue

        kept.append(camp._id)

    return [(camp._fullname, camp.link_id, camp.bid) for camp in kept]
Example #37
0
    def get(cls, flight_id):
        fullname = g.cache.get(cls._cachekey(flight_id))

        if not fullname:
            q = PromoCampaign._query(
                PromoCampaign.c.external_flight_id == flight_id,
                data=True,
            )
            q._limit = 1
            campaigns = list(q)
            if campaigns:
                campaign = campaigns[0]

                cls.add(campaign)

                return campaign._fullname
            else:
                return None
        else:
            return fullname
Example #38
0
    def _handle_adzerk(msg):
        data = json.loads(msg.body)
        g.log.debug('data: %s' % data)

        action = data.get('action')

        if action == 'deactivate_orphaned_flight':
            _deactivate_orphaned_flight(data['flight'])
            return

        link = Link._by_fullname(data['link'], data=True)
        if data['campaign']:
            campaign = PromoCampaign._by_fullname(data['campaign'], data=True)
        else:
            campaign = None

        if action == 'update_adzerk':
            _update_adzerk(link, campaign)
        elif action == 'deactivate_overdelivered':
            _deactivate_overdelivered(link, campaign)
Example #39
0
    def GET_report(self, start, end, link_text=None, owner=None):
        now = datetime.now(g.tz).replace(hour=0,
                                         minute=0,
                                         second=0,
                                         microsecond=0)
        if not start or not end:
            start = promote.promo_datetime_now(offset=1).date()
            end = promote.promo_datetime_now(offset=8).date()
            c.errors.remove((errors.BAD_DATE, 'startdate'))
            c.errors.remove((errors.BAD_DATE, 'enddate'))
        end = end or now - timedelta(days=1)
        start = start or end - timedelta(days=7)

        links = []
        bad_links = []
        owner_name = owner.name if owner else ''

        if owner:
            campaign_ids = PromotionWeights.get_campaign_ids(
                start, end, author_id=owner._id)
            campaigns = PromoCampaign._byID(campaign_ids, data=True)
            link_ids = {camp.link_id for camp in campaigns.itervalues()}
            links.extend(Link._byID(link_ids, data=True, return_dict=False))

        if link_text is not None:
            id36s = link_text.replace(',', ' ').split()
            try:
                links_from_text = Link._byID36(id36s, data=True)
            except NotFound:
                links_from_text = {}

            bad_links = [id36 for id36 in id36s if id36 not in links_from_text]
            links.extend(links_from_text.values())

        content = PromoteReport(links, link_text, owner_name, bad_links, start,
                                end)
        if c.render_style == 'csv':
            return content.as_csv()
        else:
            return PromotePage(title=_("sponsored link report"),
                               content=content).render()
Example #40
0
def filter_campaigns(date, fullnames):
    campaigns = PromoCampaign._by_fullname(fullnames,
                                           data=True,
                                           return_dict=False)

    # filter out campaigns that shouldn't be live
    pc_date = datetime.datetime(date.year,
                                date.month,
                                date.day,
                                0,
                                0,
                                tzinfo=g.tz)

    campaigns = [
        camp for camp in campaigns
        if camp.start_date <= pc_date <= camp.end_date
    ]

    # check for links with targeted campaigns - we can't handle them now
    has_targeted = [camp.link_id for camp in campaigns if camp.sr_name != '']
    return [camp for camp in campaigns if camp.link_id not in has_targeted]
Example #41
0
 def query(self):
     if c.user_is_sponsor:
         if self.sort == "future_promos":
             return queries.get_all_unapproved_links()
         elif self.sort == "pending_promos":
             return queries.get_all_accepted_links()
         elif self.sort == "unpaid_promos":
             return queries.get_all_unpaid_links()
         elif self.sort == "rejected_promos":
             return queries.get_all_rejected_links()
         elif self.sort == "live_promos" and self.sr:
             return self.live_by_subreddit(self.sr)
         elif self.sort == 'live_promos':
             return queries.get_all_live_links()
         elif self.sort == 'underdelivered':
             q = queries.get_underdelivered_campaigns()
             campaigns = PromoCampaign._by_fullname(list(q),
                                                    data=True,
                                                    return_dict=False)
             link_ids = [camp.link_id for camp in campaigns]
             return [Link._fullname_from_id36(to36(id)) for id in link_ids]
         elif self.sort == 'reported':
             return queries.get_reported_links(Subreddit.get_promote_srid())
         elif self.sort == 'house':
             return self.get_house_link_names()
         elif self.sort == 'all':
             return queries.get_all_promoted_links()
     else:
         if self.sort == "future_promos":
             return queries.get_unapproved_links(c.user._id)
         elif self.sort == "pending_promos":
             return queries.get_accepted_links(c.user._id)
         elif self.sort == "unpaid_promos":
             return queries.get_unpaid_links(c.user._id)
         elif self.sort == "rejected_promos":
             return queries.get_rejected_links(c.user._id)
         elif self.sort == "live_promos":
             return queries.get_live_links(c.user._id)
         elif self.sort == "all":
             return queries.get_promoted_links(c.user._id)
Example #42
0
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)
Example #43
0
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)
Example #44
0
    def bid_history(cls, start_date, end_date = None, account_id = None):
        from r2.lib import promote
        from r2.models import PromoCampaign
        
        if not end_date:
            end_date = datetime.datetime.now(g.tz)
        
        start_date = to_date(start_date)
        end_date   = to_date(end_date)
        q = cls.query()
        q = q.filter(and_(cls.date >= start_date, cls.date < end_date))
        q = list(q)

        links = Link._by_fullname([x.thing_name for x in q], data=True)

        d = start_date
        res = []
        while d < end_date:
            bid = 0
            refund = 0
            for i in q:
                if d == i.date:
                    l = links[i.thing_name]
                    if (not promote.is_rejected(l) and 
                        not promote.is_unpaid(l) and 
                        not l._deleted):

                        try:
                            camp = PromoCampaign._byID(i.promo_idx, data=True)
                            bid += i.bid
                            refund += i.bid if camp.is_freebie() else 0
                        except NotFound:
                            g.log.error("Skipping missing PromoCampaign in "
                                        "bidding.bid_history, campaign id: %d" 
                                        % i.promo_idx)
            res.append([d, bid, refund])
            d += datetime.timedelta(1)
        return res
Example #45
0
def get_scheduled(date, sr_name=''):
    all_promotions = PromotionWeights.get_campaigns(date)
    fp_promotions = [p for p in all_promotions if p.sr_name == sr_name]
    campaigns = PromoCampaign._byID([i.promo_idx for i in fp_promotions],
                                    return_dict=False,
                                    data=True)
    links = Link._by_fullname([i.thing_name for i in fp_promotions],
                              return_dict=False,
                              data=True)
    links = {l._id: l for l in links}
    kept = []
    for camp in campaigns:
        if camp.trans_id == 0:
            continue

        link = links[camp.link_id]
        if link._spam or not promote.is_accepted(link):
            continue

        kept.append(camp._id)

    return [('%s_%s' % (PC_PREFIX, to36(p.promo_idx)), p.thing_name, p.bid)
            for p in fp_promotions if p.promo_idx in kept]
Example #46
0
def accepted_campaigns(offset=0):
    now = promo_datetime_now(offset=offset)
    promo_weights = PromotionWeights.get_campaigns(now)
    all_links = Link._by_fullname(set(x.thing_name for x in promo_weights),
                                  data=True, return_dict=True)
    accepted_links = {}
    for link_fullname, link in all_links.iteritems():
        if is_accepted(link):
            accepted_links[link._id] = link

    accepted_link_ids = accepted_links.keys()
    campaign_query = PromoCampaign._query(PromoCampaign.c.link_id == accepted_link_ids,
                                          data=True)
    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:
            continue
        link = accepted_links.get(campaign.link_id)
        if not link:
            continue

        yield (link, campaign, pw.weight)
Example #47
0
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
Example #48
0
    def GET_report(self, start, end, link_text=None, owner=None):
        now = datetime.now(g.tz).replace(hour=0,
                                         minute=0,
                                         second=0,
                                         microsecond=0)
        end = end or now - timedelta(days=1)
        start = start or end - timedelta(days=7)

        links = []
        bad_links = []
        owner_name = owner.name if owner else ''

        if owner:
            promo_weights = PromotionWeights.get_campaigns(start,
                                                           end,
                                                           author_id=owner._id)
            campaign_ids = [pw.promo_idx for pw in promo_weights]
            campaigns = PromoCampaign._byID(campaign_ids, data=True)
            link_ids = {camp.link_id for camp in campaigns.itervalues()}
            links.extend(Link._byID(link_ids, data=True, return_dict=False))

        if link_text is not None:
            id36s = link_text.replace(',', ' ').split()
            try:
                links_from_text = Link._byID36(id36s, data=True)
            except NotFound:
                links_from_text = {}

            bad_links = [id36 for id36 in id36s if id36 not in links_from_text]
            links.extend(links_from_text.values())

        content = PromoteReport(links, link_text, owner_name, bad_links, start,
                                end)
        if c.render_style == 'csv':
            return content.as_csv()
        else:
            return PromotePage('report', content=content).render()
Example #49
0
    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())
Example #50
0
    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)
Example #51
0
    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)
Example #52
0
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)
Example #53
0
def make_daily_promotions(offset=0, test=False):
    """
    Arguments:
      offset - number of days after today to get the schedule for
      test - if True, new schedule will be generated but not launched
    Raises Exception with list of campaigns that had errors if there were any
    """

    scheduled_adweights, error_campaigns = get_scheduled(offset)
    current_adweights_byid = get_live_promotions([LiveAdWeights.ALL_ADS])
    current_adweights = current_adweights_byid[LiveAdWeights.ALL_ADS]

    link_names = [aw.link for aw in itertools.chain(scheduled_adweights,
                                                    current_adweights)]
    links = Link._by_fullname(link_names, data=True)

    camp_names = [aw.campaign for aw in itertools.chain(scheduled_adweights,
                                                        current_adweights)]
    campaigns = PromoCampaign._by_fullname(camp_names, data=True)
    srs = Subreddit._by_name([camp.sr_name for camp in campaigns.itervalues()
                              if camp.sr_name])

    expired_links = ({aw.link for aw in current_adweights} -
                     {aw.link for aw in scheduled_adweights})
    for link_name in expired_links:
        link = links[link_name]
        if is_promoted(link):
            if test:
                print "unpromote", link_name
            else:
                # update the query queue
                set_promote_status(link, PROMOTE_STATUS.finished)
                emailer.finished_promo(link)

    by_srid = defaultdict(list)
    for adweight in scheduled_adweights:
        link = links[adweight.link]
        campaign = campaigns[adweight.campaign]
        if campaign.sr_name:
            sr = srs[campaign.sr_name]
            sr_id = sr._id
            sr_over_18 = sr.over_18
        else:
            sr_id = ''
            sr_over_18 = False

        if sr_over_18:
            if test:
                print "over18", link._fullname
            else:
                link.over_18 = True
                link._commit()

        if is_accepted(link) and not is_promoted(link):
            if test:
                print "promote2", link._fullname
            else:
                # update the query queue
                set_promote_status(link, PROMOTE_STATUS.promoted)
                emailer.live_promo(link)

        by_srid[sr_id].append(adweight)

    if not test:
        set_live_promotions(by_srid)
        _mark_promos_updated()
    else:
        print by_srid

    finalize_completed_campaigns(daysago=offset+1)
    hooks.get_hook('promote.make_daily_promotions').call(offset=offset)

    # after launching as many campaigns as possible, raise an exception to 
    #   report any error campaigns. (useful for triggering alerts in irc)
    if error_campaigns:
        raise Exception("Some scheduled campaigns could not be added to daily "
                        "promotions: %r" % error_campaigns)
Example #54
0
    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 _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()
Example #56
0
    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)