diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py index 0feeb7437..f038636dc 100644 --- a/r2/r2/config/routing.py +++ b/r2/r2/config/routing.py @@ -139,6 +139,7 @@ def make_map(): mc('/framebuster/:what/:blah', controller='front', action='framebuster') + mc('/admin/promoted', controller='promote', action='admin') mc('/promoted/edit_promo/:link', controller='promote', action='edit_promo') mc('/promoted/edit_promo/pc/:campaign', controller='promote', # admin only diff --git a/r2/r2/controllers/promotecontroller.py b/r2/r2/controllers/promotecontroller.py index 4ed712875..2608954f9 100644 --- a/r2/r2/controllers/promotecontroller.py +++ b/r2/r2/controllers/promotecontroller.py @@ -552,3 +552,14 @@ class PromoteController(ListingController): errors = errors, form_id = "image-upload").render() + @validate(VSponsorAdmin(), + launchdate=VDate('ondate'), + dates=VDateRange(['startdate', 'enddate']), + query_type=VOneOf('q', ('started_on', 'between'), default=None)) + def GET_admin(self, launchdate=None, dates=None, query_type=None): + return PromoAdminTool(query_type=query_type, + launchdate=launchdate, + start=dates[0], + end=dates[1]).render() + + diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 73d4d4e09..bb8feaf58 100755 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -3218,6 +3218,65 @@ class PromoteLinkForm(Templated): bids = bids, *a, **kw) +class PromoAdminTool(Reddit): + def __init__(self, query_type=None, launchdate=None, start=None, end=None, *a, **kw): + self.query_type = query_type + self.launch = launchdate if launchdate else datetime.datetime.now() + self.start = start if start else datetime.datetime.now() + self.end = end if end else self.start + datetime.timedelta(1) + # started_on shows promos that were scheduled to launch on start date + if query_type == "started_on" and self.start: + all_promos = self.get_promo_info(self.start, + self.start + datetime.timedelta(1)) # exactly one day + promos = {} + start_date_string = self.start.strftime("%Y/%m/%d") + for camp_id, data in all_promos.iteritems(): + if start_date_string == data["campaign_start"]: + promos[camp_id] = data + # between shows any promo that was scheduled on at least one day in + # the range [start, end) + elif query_type == "between" and self.start and self.end: + promos = self.get_promo_info(self.start, self.end) + else: + promos = {} + + for camp_id, promo in promos.iteritems(): + link_id36 = promo["link_fullname"].split('_')[1] + promo["campaign_id"] = camp_id + promo["edit_link"] = promote.promo_edit_url(None, id36=link_id36) + + self.promos = sorted(promos.values(), + key=lambda x: (x['username'], x['campaign_start'])) + + Reddit.__init__(self, title="Promo Admin Tool", show_sidebar=False) + + + def get_promo_info(self, start_date, end_date): + promo_info = {} + scheduled = Promote_Graph.get_current_promos(start_date, + end_date + datetime.timedelta(1)) + campaign_ids = [x[1] for x in scheduled] + campaigns = PromoCampaign._byID(campaign_ids, data=True, return_dict=True) + account_ids = [pc.owner_id for pc in campaigns.itervalues()] + accounts = Account._byID(account_ids, data=True, return_dict=True) + for link, campaign_id, scheduled_start, scheduled_end in scheduled: + campaign = campaigns[campaign_id] + days = (campaign.end_date - campaign.start_date).days + bid_per_day = float(campaign.bid) / days + account = accounts[campaign.owner_id] + promo_info[campaign._id] = { + 'username': account.name, + 'user_email': account.email, + 'link_title': link.title, + 'link_fullname': link._fullname, + 'campaign_start': campaign.start_date.strftime("%Y/%m/%d"), + 'campaign_end': campaign.end_date.strftime("%Y/%m/%d"), + 'bid_per_day': bid_per_day, + } + return promo_info + + + class Roadblocks(Templated): def __init__(self): self.roadblocks = promote.get_roadblocks() diff --git a/r2/r2/lib/promote.py b/r2/r2/lib/promote.py index 5ee506ba5..57d61f4de 100644 --- a/r2/r2/lib/promote.py +++ b/r2/r2/lib/promote.py @@ -68,9 +68,10 @@ def promotraffic_url(l): # new traffic url domain = get_domain(cname=False, subreddit=False) return "http://%s/promoted/traffic/headline/%s" % (domain, l._id36) -def promo_edit_url(l): +def promo_edit_url(l=None, id36=""): + if l: id36 = l._id36 domain = get_domain(cname = False, subreddit = False) - return "http://%s/promoted/edit_promo/%s" % (domain, l._id36) + return "http://%s/promoted/edit_promo/%s" % (domain, id36) def pay_url(l, campaign): return "%spromoted/pay/%s/%s" % (g.payment_domain, l._id36, campaign._id36) diff --git a/r2/r2/public/static/css/reddit.css b/r2/r2/public/static/css/reddit.css index 229bd9194..973267421 100755 --- a/r2/r2/public/static/css/reddit.css +++ b/r2/r2/public/static/css/reddit.css @@ -4220,6 +4220,23 @@ div.timeseries span.title { padding: 20px; } +.promo-admin-form { + padding: 10px; +} + +.promo-admin-form button { + margin-bottom: 10px; +} + +.promo-admin-data th { + font-weight: bold; +} + +.promo-admin-data td, .promo-admin-data th { + padding: 1px 4px; + white-space: nowrap; +} + p.totals-are-preliminary { margin-left: 10px; } diff --git a/r2/r2/templates/promoadminpage.html b/r2/r2/templates/promoadminpage.html new file mode 100644 index 000000000..468bb3c4e --- /dev/null +++ b/r2/r2/templates/promoadminpage.html @@ -0,0 +1,43 @@ +## 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-2012 +## reddit Inc. All Rights Reserved. +############################################################################### + +<%inherit file="reddit.html"/> + +<%def name="stylesheet()"> + ${parent.stylesheet()} + + + diff --git a/r2/r2/templates/promoadmintool.html b/r2/r2/templates/promoadmintool.html new file mode 100644 index 000000000..d9f7f3bf9 --- /dev/null +++ b/r2/r2/templates/promoadmintool.html @@ -0,0 +1,109 @@ +## 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-2012 +## reddit Inc. All Rights Reserved. +############################################################################### + +<%inherit file="reddit.html"/> + +<%! + from r2.lib.template_helpers import static + from r2.lib import js + %> + +<%namespace name="pr" file="promotelinkform.html" /> +<%namespace name="utils" file="utils.html"/> + +<%def name="javascript()"> + ${parent.javascript()} + ${unsafe(js.use('sponsored'))} + + +<%def name="bodyHTML()"> +
+
+
+ + + see ads that started on: + + <%pr:datepicker name="ondate" value="${thing.start.strftime('%m/%d/%Y')}" + minDateSrc="date-min" initfuncname="init_ondate" min_date_offset="86400000"> + function(elem) { return elem; } + +
+
+ + see ads that were running any day between: + + <%pr:datepicker name="startdate" value="${thing.launch.strftime('%m/%d/%Y')}" + minDateSrc="date-min" initfuncname="init_startdate" min_date_offset="86400000"> + function(elem) { return elem; } + + and + <%pr:datepicker name="enddate" value="${thing.end.strftime('%m/%d/%Y')}" + minDateSrc="date-min" initfuncname="init_enddate" min_date_offset="86400000"> + function(elem) { return elem; } + + (inclusive) +
+
+ +
+
+ + + ## init date pickers + + +
+ + + + + + + + + + + + + %for p in thing.promos: + + + + + + + + + + + %endfor + +
campaignusernameuser emailcampaign startcampaign endbid per dayedit linklink title
${p['campaign_id']}${p['username']}${p['user_email']}${p['campaign_start']}${p['campaign_end']}$${"%0.2f" % p['bid_per_day']}${p['edit_link']}${p['link_title']}
+
+