mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-26 15:28:37 -05:00
Delete Promote_Graph.
This commit is contained in:
@@ -208,7 +208,6 @@ def make_map():
|
||||
controller='promote', action='pay')
|
||||
mc('/promoted/refund/:link/:campaign', controller='promote',
|
||||
action='refund')
|
||||
mc('/promoted/admin/graph', controller='promote', action='admingraph')
|
||||
|
||||
mc('/promoted/:action', controller='promote',
|
||||
requirements=dict(action="edit_promo|new_promo|roadblock"))
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from babel.numbers import format_number
|
||||
import itertools
|
||||
import json
|
||||
import urllib
|
||||
|
||||
@@ -40,7 +39,6 @@ from r2.lib.menus import NamedButton, NavButton, NavMenu
|
||||
from r2.lib.pages import (
|
||||
LinkInfoPage,
|
||||
PaymentForm,
|
||||
Promote_Graph,
|
||||
PromotePage,
|
||||
PromoteLinkForm,
|
||||
PromoteLinkNew,
|
||||
@@ -107,27 +105,6 @@ from r2.models import (
|
||||
)
|
||||
|
||||
|
||||
def _check_dates(dates):
|
||||
params = ('startdate', 'enddate')
|
||||
error_types = (errors.BAD_DATE,
|
||||
errors.DATE_TOO_EARLY,
|
||||
errors.DATE_TOO_LATE,
|
||||
errors.BAD_DATE_RANGE,
|
||||
errors.DATE_RANGE_TOO_LARGE)
|
||||
for error_case in itertools.product(error_types, params):
|
||||
if error_case in c.errors:
|
||||
bad_dates = dates
|
||||
start, end = None, None
|
||||
break
|
||||
else:
|
||||
bad_dates = None
|
||||
start, end = dates
|
||||
if not start or not end:
|
||||
start = promote.promo_datetime_now(offset=-7).date()
|
||||
end = promote.promo_datetime_now(offset=6).date()
|
||||
return start, end, bad_dates
|
||||
|
||||
|
||||
def campaign_has_oversold_error(form, campaign):
|
||||
if campaign.priority.inventory_override:
|
||||
return
|
||||
@@ -323,21 +300,6 @@ class PromoteController(ListingController):
|
||||
datestr=True)
|
||||
return {'inventory': available_by_datestr}
|
||||
|
||||
@validate(VSponsor(),
|
||||
dates=VDateRange(["startdate", "enddate"],
|
||||
max_range=timedelta(days=28),
|
||||
required=False))
|
||||
@validate(VSponsorAdmin(),
|
||||
dates=VDateRange(["startdate", "enddate"],
|
||||
max_range=timedelta(days=28),
|
||||
required=False))
|
||||
def GET_admingraph(self, dates):
|
||||
start, end, bad_dates = _check_dates(dates)
|
||||
content = Promote_Graph(start, end, bad_dates=bad_dates)
|
||||
if c.render_style == 'csv':
|
||||
return content.as_csv()
|
||||
return PromotePage("admingraph", content=content).render()
|
||||
|
||||
# ## POST controllers below
|
||||
@validatedForm(VSponsorAdmin(),
|
||||
link=VLink("link_id"),
|
||||
|
||||
@@ -167,7 +167,6 @@ menu = MenuHandler(hot = _('hot'),
|
||||
all_promos = _('all'),
|
||||
future_promos = _('unseen'),
|
||||
roadblock = _('roadblock'),
|
||||
admin_graph = _('admin analytics'),
|
||||
live_promos = _('live'),
|
||||
unpaid_promos = _('unpaid'),
|
||||
pending_promos = _('pending'),
|
||||
|
||||
@@ -3427,8 +3427,6 @@ class PromotePage(Reddit):
|
||||
buttons.append(NamedButton('my_current_promos', dest = ''))
|
||||
|
||||
if c.user_is_sponsor:
|
||||
buttons.append(NamedButton('admin_graph',
|
||||
dest='/admin/graph'))
|
||||
buttons.append(NavButton('report', 'report'))
|
||||
buttons.append(NavButton('underdelivered', 'underdelivered'))
|
||||
buttons.append(NavButton('house ads', 'house'))
|
||||
@@ -3788,196 +3786,6 @@ class Promotion_Summary(Templated):
|
||||
% ndays, p.render('email'))
|
||||
|
||||
|
||||
def force_datetime(d):
|
||||
return datetime.datetime.combine(d, datetime.time())
|
||||
|
||||
|
||||
class Promote_Graph(Templated):
|
||||
|
||||
@classmethod
|
||||
@memoize('get_market', time = 60)
|
||||
def get_market(cls, user_id, start_date, end_date):
|
||||
market = {}
|
||||
promo_counter = {}
|
||||
def callback(link, bid_day, starti, endi, campaign):
|
||||
for i in xrange(starti, endi):
|
||||
if user_id is None or link.author_id == user_id:
|
||||
if (not promote.is_unpaid(link) and
|
||||
not promote.is_rejected(link) and
|
||||
campaign.trans_id != NO_TRANSACTION):
|
||||
market[i] = market.get(i, 0) + bid_day
|
||||
promo_counter[i] = promo_counter.get(i, 0) + 1
|
||||
cls.promo_iter(start_date, end_date, callback)
|
||||
return market, promo_counter
|
||||
|
||||
@classmethod
|
||||
def promo_iter(cls, start_date, end_date, callback):
|
||||
size = (end_date - start_date).days
|
||||
current_promos = cls.get_current_promos(start_date, end_date)
|
||||
campaign_ids = [camp_id for link, camp_id, s, e in current_promos]
|
||||
campaigns = PromoCampaign._byID(campaign_ids, data=True)
|
||||
for link, campaign_id, s, e in current_promos:
|
||||
if campaign_id in campaigns:
|
||||
campaign = campaigns[campaign_id]
|
||||
sdate = campaign.start_date.date()
|
||||
edate = campaign.end_date.date()
|
||||
starti = max((sdate - start_date).days, 0)
|
||||
endi = min((edate - start_date).days, size)
|
||||
bid_day = campaign.bid / max((edate - sdate).days, 1)
|
||||
callback(link, bid_day, starti, endi, campaign)
|
||||
|
||||
@classmethod
|
||||
def get_current_promos(cls, start_date, end_date):
|
||||
# grab promoted links
|
||||
# returns a list of (thing_id, campaign_idx, start, end)
|
||||
promos = PromotionWeights.get_schedule(start_date, end_date)
|
||||
# sort based on the start date
|
||||
promos.sort(key = lambda x: x[2])
|
||||
|
||||
# wrap the links
|
||||
links = wrap_links([p[0] for p in promos])
|
||||
# remove rejected/unpaid promos
|
||||
links = dict((l._fullname, l) for l in links.things
|
||||
if promote.is_accepted(l) or promote.is_unapproved(l))
|
||||
# filter promos accordingly
|
||||
promos = [(links[thing_name], campaign_id, s, e)
|
||||
for thing_name, campaign_id, s, e in promos
|
||||
if links.has_key(thing_name)]
|
||||
|
||||
return promos
|
||||
|
||||
def __init__(self, start_date, end_date, bad_dates=None):
|
||||
self.now = promote.promo_datetime_now()
|
||||
|
||||
start_date = to_date(start_date)
|
||||
end_date = to_date(end_date)
|
||||
end_before = end_date + datetime.timedelta(days=1)
|
||||
|
||||
size = (end_before - start_date).days
|
||||
self.dates = [start_date + datetime.timedelta(i) for i in xrange(size)]
|
||||
|
||||
# these will be cached queries
|
||||
market, promo_counter = self.get_market(None, start_date, end_before)
|
||||
my_market = market
|
||||
|
||||
# determine the range of each link
|
||||
promote_blocks = []
|
||||
def block_maker(link, bid_day, starti, endi, campaign):
|
||||
if (not promote.is_rejected(link)
|
||||
and not promote.is_unpaid(link)):
|
||||
promote_blocks.append((link, starti, endi, campaign))
|
||||
self.promo_iter(start_date, end_before, block_maker)
|
||||
|
||||
# now sort the promoted_blocks into the most contiguous chuncks we can
|
||||
sorted_blocks = []
|
||||
while promote_blocks:
|
||||
cur = promote_blocks.pop(0)
|
||||
while True:
|
||||
sorted_blocks.append(cur)
|
||||
# get the future items (sort will be preserved)
|
||||
future = filter(lambda x: x[2] >= cur[3], promote_blocks)
|
||||
if future:
|
||||
# resort by date and give precidence to longest promo:
|
||||
cur = min(future, key = lambda x: (x[2], x[2]-x[3]))
|
||||
promote_blocks.remove(cur)
|
||||
else:
|
||||
break
|
||||
|
||||
pool =PromotionWeights.bid_history(promote.promo_datetime_now(offset=-30),
|
||||
promote.promo_datetime_now(offset=2))
|
||||
|
||||
# graphs of impressions and clicks
|
||||
self.promo_traffic = promote.traffic_totals()
|
||||
|
||||
impressions = [(d, i) for (d, (i, k)) in self.promo_traffic]
|
||||
pool = dict((d, b+r) for (d, b, r) in pool)
|
||||
|
||||
if impressions:
|
||||
CPM = [(force_datetime(d), (pool.get(d, 0) * 1000. / i) if i else 0)
|
||||
for (d, (i, k)) in self.promo_traffic if d in pool]
|
||||
mean_CPM = sum(x[1] for x in CPM) * 1. / max(len(CPM), 1)
|
||||
|
||||
CPC = [(force_datetime(d), (100 * pool.get(d, 0) / k) if k else 0)
|
||||
for (d, (i, k)) in self.promo_traffic if d in pool]
|
||||
mean_CPC = sum(x[1] for x in CPC) * 1. / max(len(CPC), 1)
|
||||
|
||||
cpm_title = _("cost per 1k impressions ($%(avg).2f average)") % dict(avg=mean_CPM)
|
||||
cpc_title = _("cost per click ($%(avg).2f average)") % dict(avg=mean_CPC/100.)
|
||||
|
||||
data = traffic.zip_timeseries(((d, (min(v, mean_CPM * 2),)) for d, v in CPM),
|
||||
((d, (min(v, mean_CPC * 2),)) for d, v in CPC))
|
||||
|
||||
from r2.lib.pages.trafficpages import COLORS # not top level because of * imports :(
|
||||
self.performance_table = TimeSeriesChart("promote-graph-table",
|
||||
_("historical performance"),
|
||||
"day",
|
||||
[dict(color=COLORS.DOWNVOTE_BLUE,
|
||||
title=cpm_title,
|
||||
shortname=_("CPM")),
|
||||
dict(color=COLORS.DOWNVOTE_BLUE,
|
||||
title=cpc_title,
|
||||
shortname=_("CPC"))],
|
||||
data)
|
||||
else:
|
||||
self.performance_table = None
|
||||
|
||||
self.promo_traffic = dict(self.promo_traffic)
|
||||
|
||||
predicted = inventory.get_predicted_by_date(None, start_date,
|
||||
end_before)
|
||||
self.impression_inventory = predicted
|
||||
# TODO: Real data
|
||||
self.scheduled_impressions = dict.fromkeys(predicted, 0)
|
||||
|
||||
self.cpc = {}
|
||||
self.cpm = {}
|
||||
self.delivered = {}
|
||||
self.clicked = {}
|
||||
self.my_market = {}
|
||||
self.promo_counter = {}
|
||||
|
||||
today = self.now.date()
|
||||
for i in xrange(size):
|
||||
day = start_date + datetime.timedelta(i)
|
||||
cpc = cpm = delivered = clicks = "---"
|
||||
if day in self.promo_traffic:
|
||||
delivered, clicks = self.promo_traffic[day]
|
||||
if i in market and day < today:
|
||||
cpm = "$%.2f" % promote.cost_per_mille(market[i], delivered)
|
||||
cpc = "$%.2f" % promote.cost_per_click(market[i], clicks)
|
||||
delivered = format_number(delivered, c.locale)
|
||||
clicks = format_number(clicks, c.locale)
|
||||
if day == today:
|
||||
delivered = "(%s)" % delivered
|
||||
clicks = "(%s)" % clicks
|
||||
self.cpc[day] = cpc
|
||||
self.cpm[day] = cpm
|
||||
self.delivered[day] = delivered
|
||||
self.clicked[day] = clicks
|
||||
if i in my_market:
|
||||
self.my_market[day] = "$%.2f" % my_market[i]
|
||||
else:
|
||||
self.my_market[day] = "---"
|
||||
self.promo_counter[day] = promo_counter.get(i, "---")
|
||||
|
||||
Templated.__init__(self, today=today, promote_blocks=sorted_blocks,
|
||||
start_date=start_date, end_date=end_date,
|
||||
bad_dates=bad_dates)
|
||||
|
||||
def to_iter(self, localize = True):
|
||||
locale = c.locale
|
||||
def num(x):
|
||||
if localize:
|
||||
return format_number(x, locale)
|
||||
return str(x)
|
||||
for link, uimp, nimp, ucli, ncli in self.recent:
|
||||
yield (link._date.strftime("%Y-%m-%d"),
|
||||
num(uimp), num(nimp), num(ucli), num(ncli),
|
||||
num(link._ups - link._downs),
|
||||
"$%.2f" % link.promote_bid,
|
||||
_force_unicode(link.title))
|
||||
|
||||
|
||||
class PromoteReport(Templated):
|
||||
def __init__(self, links, link_text, owner_name, bad_links, start, end):
|
||||
self.links = links
|
||||
|
||||
@@ -5248,70 +5248,6 @@ button.new-campaign:disabled { color: gray; }
|
||||
dt { margin-left: 10px; font-weight: bold; }
|
||||
dd { margin-left: 20px; }
|
||||
|
||||
.calendar-lead {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
table.calendar {
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
padding-top: 120px;
|
||||
padding-bottom: 100px;
|
||||
empty-cells: show;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.calendar .today {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.calendar thead tr {
|
||||
border-bottom: 2px solid gray;
|
||||
}
|
||||
|
||||
.calendar thead th, .calendar thead td {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.calendar td, .calendar th[scope="col"] {
|
||||
border-left: 1px dashed gray;
|
||||
}
|
||||
|
||||
.calendar .blob {
|
||||
margin: 0;
|
||||
padding: 0px;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar .blob.link:hover {
|
||||
border: 1px solid red;
|
||||
box-shadow: 2px 2px 2px #000;
|
||||
-moz-box-shadow: 2px 2px 2px #000;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.calendar .blob .bid {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.calendar .blob.link {
|
||||
margin: -1px -2px 0px -2px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.calendar .blob.link .title{
|
||||
font-size: small;
|
||||
padding: 0 5px 5px 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.borderless td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be
|
||||
## consistent with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is reddit Inc.
|
||||
##
|
||||
## All portions of the code written by reddit are Copyright (c) 2006-2013
|
||||
## reddit Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
<%!
|
||||
from r2.lib import js
|
||||
from r2.lib.template_helpers import static
|
||||
%>
|
||||
|
||||
<%namespace file="reddittraffic.html" import="load_timeseries_js"/>
|
||||
<%namespace name="pr" file="promotelinkform.html" />
|
||||
|
||||
${load_timeseries_js()}
|
||||
${unsafe(js.use('sponsored'))}
|
||||
|
||||
<h1>${_("Sponsored link calendar")}</h1>
|
||||
<div class="calendar-lead">
|
||||
<h2>${_("Viewing calendar for dates:")}</h2>
|
||||
|
||||
%if thing.bad_dates and all(thing.bad_dates):
|
||||
<div class="error">
|
||||
${_("Sorry, I couldn't make sense of the date range: %(start)s - %(end)s") %\
|
||||
dict(start=thing.bad_dates[0].strftime('%m/%d/%Y'),
|
||||
end=thing.bad_dates[1].strftime('%m/%d/%Y'))}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
<form action="/promoted/admin/graph" method="get">
|
||||
<%pr:datepicker name="startdate" value="${thing.start_date.strftime('%m/%d/%Y')}"
|
||||
minDateSrc="date-min" initfuncname="init_startdate"
|
||||
min_date_offset="86400000">
|
||||
function(elem) { check_enddate(elem, $("#enddate")); return elem; }
|
||||
</%pr:datepicker>
|
||||
–
|
||||
<%pr:datepicker name="enddate" value="${thing.end_date.strftime('%m/%d/%Y')}"
|
||||
minDateSrc="startdate" initfuncname="init_enddate"
|
||||
min_date_offset="86400000">
|
||||
function(elem) { return elem; }
|
||||
</%pr:datepicker>
|
||||
<input type="submit" value="${_("go")}"></input>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table class="calendar">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="row">${_("Date")}</th>
|
||||
%for date in thing.dates:
|
||||
<th scope="col" class="${"today" if date == thing.today else ""}">
|
||||
<time>${date}</time>
|
||||
</th>
|
||||
%endfor
|
||||
</tr>
|
||||
<tr>
|
||||
## TRANSLATORS: Noun
|
||||
<th scope="row">${_("Count")}</th>
|
||||
%for date in thing.dates:
|
||||
<td class="${"today" if date == thing.today else ""}">
|
||||
${thing.promo_counter[date]}
|
||||
</td>
|
||||
%endfor
|
||||
</tr>
|
||||
%if thing.scheduled_impressions:
|
||||
<tr>
|
||||
## TRANSLATORS: Advertising term; "ad impressions scheduled for delivery"
|
||||
<th scope="row">${_("Scheduled")}<br/>
|
||||
${_("Inventory")}</th>
|
||||
%for date in thing.dates:
|
||||
<td class="${"today" if date == thing.today else ""}">
|
||||
${thing.scheduled_impressions.get(date, "---")}<br/>
|
||||
${thing.impression_inventory.get(date, "---")}
|
||||
</td>
|
||||
%endfor
|
||||
</tr>
|
||||
%endif
|
||||
<tr>
|
||||
<th scope="row">
|
||||
## TRANSLATORS: Advertising term; "ad impressions delivered / shown"
|
||||
${_("Impressions")}<br/>
|
||||
## TRANSLATORS: Advertising term: "cost per mille"
|
||||
${_("CPM")}
|
||||
</th>
|
||||
%for date in thing.dates:
|
||||
<td class="${"today" if date == thing.today else ""}">
|
||||
${thing.delivered[date]}<br/>
|
||||
${thing.cpm[date]}
|
||||
</td>
|
||||
%endfor
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
${_("Clicks")}<br/>
|
||||
## TRANSLATORS: Advertising term: "cost per click"
|
||||
${_("CPC")}
|
||||
</th>
|
||||
%for date in thing.dates:
|
||||
<td class="${"today" if date == thing.today else ""}">
|
||||
${thing.clicked[date]}<br/>
|
||||
${thing.cpc[date]}
|
||||
</td>
|
||||
%endfor
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
%if c.user_is_sponsor:
|
||||
## TRANSLATORS: Advertising term: Total value of scheduled ads
|
||||
${_("Total Commit")}
|
||||
%else:
|
||||
## TRANSLATORS: Advertising term: Amount spent on scheduled ads
|
||||
${_("Your Commit")}
|
||||
%endif
|
||||
</th>
|
||||
%for date in thing.dates:
|
||||
<td class="${"today" if date == thing.today else ""}">
|
||||
${thing.my_market[date]}
|
||||
</td>
|
||||
%endfor
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
%for link, start, end, campaign in thing.promote_blocks:
|
||||
<tr>
|
||||
<th class="placeholder"></th>
|
||||
%for i in xrange(start):
|
||||
<td class="placeholder
|
||||
${"today" if thing.dates[i] == thing.today else ""}"></td>
|
||||
%endfor
|
||||
<td colspan="${end - start}">
|
||||
<div class="blob promotedlink ${link.rowstyle} rounded"
|
||||
title="${link.title}">
|
||||
<div class="bid">
|
||||
${campaign.sr_name + ':' if campaign.sr_name else ''}
|
||||
$${int(campaign.bid)}
|
||||
</div>
|
||||
<a class="title" href="/promoted/edit_promo/${link._id36}"
|
||||
title="${link.title}">
|
||||
${link.author.name}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
%for i in xrange(end, len(thing.dates)):
|
||||
<td class="placeholder
|
||||
${"today" if thing.dates[i] == thing.today else ""}"></td>
|
||||
%endfor
|
||||
</tr>
|
||||
%endfor
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<h1>${_("historical site performance")}</h1>
|
||||
<div id="charts"></div>
|
||||
|
||||
${thing.performance_table}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
init_startdate();
|
||||
init_enddate();
|
||||
});
|
||||
r.timeseries.init();
|
||||
|
||||
</script>
|
||||
</div>
|
||||
Reference in New Issue
Block a user