Add reddit gold partners

This commit is contained in:
Chad Birch
2013-05-23 16:19:07 -07:00
parent b8f0d49f48
commit 3ec750698d
11 changed files with 254 additions and 18 deletions

View File

@@ -214,6 +214,7 @@ def make_map():
mc('/gold', controller='forms', action="gold")
mc('/gold/creditgild/:passthrough', controller='forms', action='creditgild')
mc('/gold/about', controller='front', action='gold_info')
mc('/gold/partners', controller='front', action='gold_partners')
mc('/gold/thanks', controller='front', action='goldthanks')
mc('/password', controller='forms', action="password")

View File

@@ -849,6 +849,17 @@ class ApiController(RedditController, OAuth2ResourceController):
notify_user_added("accept_moderator_invite", c.user, c.user, c.site)
jquery.refresh()
@json_validate(VUser(),
VGold(),
VModhash(),
deal=VLength('deal', 100))
def POST_claim_gold_partner_deal_code(self, responder, deal):
try:
return {'code': GoldPartnerDealCode.claim_code(c.user, deal)}
except GoldPartnerCodesExhaustedError:
return {'error': 'GOLD_PARTNER_CODES_EXHAUSTED',
'explanation': _("sorry, we're out of codes!")}
@validatedForm(VUser('curpass', default=''),
VModhash(),
password=VPassword(

View File

@@ -1133,6 +1133,9 @@ class FrontController(RedditController, OAuth2ResourceController):
def GET_gold_info(self):
return GoldInfoPage(_("gold"), show_sidebar=False).render()
def GET_gold_partners(self):
return GoldPartnersPage(_("gold partners"), show_sidebar=False).render()
class FormsController(RedditController):

View File

@@ -27,6 +27,7 @@ from r2.models import Friends, All, Sub, NotFound, DomainSR, Random, Mod, Random
from r2.models import Link, Printable, Trophy, bidding, PromoCampaign, PromotionWeights, Comment
from r2.models import Flair, FlairTemplate, FlairTemplateBySubredditIndex
from r2.models import USER_FLAIR, LINK_FLAIR
from r2.models import GoldPartnerDealCode
from r2.models.promo import NO_TRANSACTION, PromotionLog
from r2.models.token import OAuth2Client, OAuth2AccessToken
from r2.models import traffic
@@ -3997,6 +3998,18 @@ class GoldInfoPage(BoringPage):
}
BoringPage.__init__(self, *args, **kwargs)
class GoldPartnersPage(BoringPage):
def __init__(self, *args, **kwargs):
self.prices = {
"gold_month_price": g.gold_month_price,
"gold_year_price": g.gold_year_price,
}
if c.user_is_loggedin:
self.existing_codes = GoldPartnerDealCode.get_codes_for_user(c.user)
else:
self.existing_codes = []
BoringPage.__init__(self, *args, **kwargs)
class Goldvertisement(Templated):
def __init__(self):
Templated.__init__(self)

View File

@@ -153,6 +153,7 @@ string_dict = dict(
gold_summary_anonymous_gift = _("You're about to give %(amount)s of reddit gold to %(recipient)s. It will be an anonymous gift."),
gold_summary_comment_gift = _("Want to say thanks to *%(recipient)s* for this comment? Give them a month of [reddit gold](/gold/about)."),
gold_summary_comment_page = _("Give *%(recipient)s* a month of [reddit gold](/gold/about) for this comment:"),
gold_partners_description = _('reddit gold members get access to exclusive stuff'),
unvotable_message = _("sorry, this has been archived and can no longer be voted on"),
account_activity_blurb = _("This page shows a history of recent activity on your account. If you notice unusual activity, you should change your password immediately. Location information is guessed from your computer's IP address and may be wildly wrong, especially for visits from mobile devices. Note: due to a bug, private-use addresses (starting with 10.) sometimes show up erroneously in this list after regular use of the site."),
your_current_ip_is = _("You are currently accessing reddit from this IP address: %(address)s."),

View File

@@ -23,9 +23,17 @@
from r2.lib.db.tdb_sql import make_metadata, index_str, create_table
from pylons import g, c
from pylons.i18n import _
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy.exc import IntegrityError
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError, OperationalError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.schema import Column
from sqlalchemy.sql import and_
from sqlalchemy.types import String, Integer
from xml.dom.minidom import Document
from r2.lib.utils import tup, randstr
@@ -42,6 +50,9 @@ ENGINE_NAME = 'authorize'
ENGINE = g.dbm.get_engine(ENGINE_NAME)
METADATA = make_metadata(ENGINE)
Session = scoped_session(sessionmaker(bind=ENGINE))
Base = declarative_base(bind=ENGINE)
gold_table = sa.Table('reddit_gold', METADATA,
sa.Column('trans_id', sa.String, nullable = False,
primary_key = True),
@@ -66,6 +77,61 @@ indices = [index_str(gold_table, 'status', 'status'),
index_str(gold_table, 'subscr_id', 'subscr_id')]
create_table(gold_table, indices)
class GoldPartnerCodesExhaustedError(Exception):
pass
class GoldPartnerDealCode(Base):
"""Promo codes for deals from reddit gold partners."""
__tablename__ = "reddit_gold_partner_deal_codes"
id = Column(Integer, primary_key=True)
deal = Column(String, nullable=False)
code = Column(String, nullable=False)
user = Column(Integer, nullable=True)
@classmethod
def get_codes_for_user(cls, user):
results = Session.query(cls).filter(cls.user == user._id)
codes = {r.deal: r.code for r in results}
return codes
@classmethod
def claim_code(cls, user, deal):
# check if they already have a code for this deal and return it
try:
result = (Session.query(cls)
.filter(and_(cls.user == user._id,
cls.deal == deal))
.one())
return result.code
except NoResultFound:
pass
# select an unclaimed code, assign it to the user, and return it
try:
claiming = (Session.query(cls)
.filter(and_(cls.deal == deal,
cls.user == None,
func.pg_try_advisory_lock(cls.id)))
.limit(1)
.one())
except NoResultFound:
raise GoldPartnerCodesExhaustedError
claiming.user = user._id
Session.add(claiming)
Session.commit()
# release the lock
Session.query(func.pg_advisory_unlock_all()).all()
return claiming.code
def create_unclaimed_gold (trans_id, payer_email, paying_id,
pennies, days, secret, date,
subscr_id = None):

View File

@@ -6,12 +6,12 @@
transform: @arguments;
}
section#about-gold, section#postcard, .or-box {
section#about-gold, section#about-gold-partners, section#postcard, .or-box {
font-family: "Bitstream Charter", "Hoefler Text", "Palatino Linotype",
"Book Antiqua", Palatino, georgia, garamond, FreeSerif, serif;
}
section#about-gold {
section#about-gold, section#about-gold-partners {
width: 960px;
margin: 40px auto;
background: url(../gold/inner-bg.jpg) 11px repeat-y;
@@ -92,6 +92,18 @@ section#about-gold {
}
}
.claim-code-button {
.buy-gold-button;
cursor: pointer;
border-width: 1px;
padding: 3px 5px;
font-size: 16px;
line-height: 20px;
float: right;
margin-top: 5px;
}
header {
// could use box-sizing here, but want to look semi-decent in IE7
@width: 960px;
@@ -243,6 +255,31 @@ section#about-gold {
}
}
section#about-gold-partners {
input.code {
font-weight: bold;
font-size: 16px;
float: right;
max-width: 200px;
line-height: 20px;
text-align: center;
}
span.error {
font-size: 16px;
float: right;
}
section {
.how-to-use p {
font-size: 13px;
font-style: italic;
margin-top: 10px;
}
}
}
.or-box {
background: #817461;
margin: 0 auto;

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -13,6 +13,10 @@ r.gold = {
})
$('.stripe-submit').on('click', this.makeStripeToken)
$('section#about-gold-partners').on('click', 'input.code', function() {
$(this).select()
})
},
_toggleCommentGoldForm: function (e) {
@@ -183,6 +187,25 @@ r.gold = {
)
}
return false
},
claim_gold_partner_deal_code: function (elem, name) {
$.ajax({
type: 'POST',
dataType: 'json',
url: '/api/claim_gold_partner_deal_code.json',
data: {'deal': name, 'uh': r.config.modhash},
success: function(data) {
if ('error' in data) {
var $newelem = $('<span class="error">').text(data['explanation'])
$(elem).replaceWith($newelem)
} else {
var $newelem = $('<input type="text" class="code">').attr('value', data['code'])
$(elem).replaceWith($newelem)
$newelem.select()
}
}
})
}
};

View File

@@ -20,6 +20,7 @@
## reddit Inc. All Rights Reserved.
###############################################################################
<%!
from r2.lib.strings import strings
from r2.lib.template_helpers import static
%>
<%namespace file="less.html" import="less_stylesheet"/>
@@ -39,22 +40,38 @@
<div class="new-mark">${_('New!')}</div>
%endif
${_md(description_md)}
%if caller:
${caller.body()}
%endif
</div>
</section>
</%def>
<%def name="goldinfo_header(title, description)">
<header>
<img class="insignia" src="${static('gold/gold-insignia-big.png')}" alt="">
<h1>${title}</h1>
<p>${description}</p>
<div class="actions">
<a class="buy-gold-button" href="/gold">${_('buy reddit gold')}</a>
<span class="or">${_('or')}</span>
<a class="postcard-button" href="/gold/about#postcard"><img src="${static('gold/stamp.png')}" alt=""> ${_('send us a postcard')}</a>
</div>
</header>
</%def>
<%def name="goldinfo_footer()">
<footer>
<h1>${_('become a member today.')}</h1>
<p>${_('reddit gold is %(gold_month_price)s / month, or %(gold_year_price)s for a year.') % thing.prices}</p>
<a class="buy-gold-button" href="/gold">${_('buy reddit gold')}</a>
</footer>
</%def>
<%def name="content()">
<section id="about-gold">
<header>
<img class="insignia" src="${static('gold/gold-insignia-big.png')}" alt="">
<h1>${_('Make reddit better.')}</h1>
<p>${_('reddit gold adds shiny extra features to your account that are made possible thanks to support from people like you.')}</p>
<div class="actions">
<a class="buy-gold-button" href="/gold">${_('buy reddit gold')}</a>
<span class="or">${_('or')}</span>
<a class="postcard-button" href="#postcard"><img src="${static('gold/stamp.png')}" alt=""> ${_('send us a postcard')}</a>
</div>
</header>
${goldinfo_header(_('Make reddit better.'),
_('reddit gold adds shiny extra features to your account that are made possible thanks to support from people like you.'))}
${feature_item(static('gold/sample-filterall.png'),
_(
"# Filter specific subreddits from /r/all.\n"
@@ -99,15 +116,12 @@
<ul>
<li><strong>${_('The Lounge.')}</strong>&#32;${_('Access to a super-secret members-only community that may or may not exist.')}</li>
<li><strong>${_('Beta test new features.')}</strong>&#32;${_('We occasionally invite gold members to try out new features first.')}</li>
<li><strong><a href="/gold/partners">${_('Deals from gold partners.')}</a></strong>&#32;${strings.gold_partners_description}</li>
<li><strong>${_('A trophy on your user page.')}</strong>&#32;${_('As thanks for supporting reddit gold.')}</li>
<li><strong>${_('More to come.')}</strong>&#32;${_("We have many more ideas for gold features, and will add to this list in the future.")}</li>
</ul>
</section>
<footer>
<h1>${_('become a member today.')}</h1>
<p>${_('reddit gold is %(gold_month_price)s / month, or %(gold_year_price)s for a year.') % thing.prices}</p>
<a class="buy-gold-button" href="/gold">${_('buy reddit gold')}</a>
</footer>
${goldinfo_footer()}
</section>
<div class="or-box">${_('or')}</div>
<a class="postcard" href="/about/postcards" target="_blank">

View File

@@ -0,0 +1,67 @@
## 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.strings import strings
from r2.lib.template_helpers import static
%>
<%namespace file="goldinfopage.html" import="feature_item, goldinfo_header, goldinfo_footer"/>
<%namespace file="less.html" import="less_stylesheet"/>
<%namespace file="utils.html" import="_md"/>
<%inherit file="reddit.html"/>
<%def name="stylesheet()">
${parent.stylesheet()}
${less_stylesheet('goldinfo.less')}
</%def>
<%def name="partner_item(name, how_to_use, img_src, description_md, extra_class='')">
<%call expr="feature_item(img_src, description_md, extra_class)">
%if c.user.gold:
<div class="how-to-use">
${_md(how_to_use)}
</div>
%endif
%if name in thing.existing_codes:
<input class="code" type="text" value="${thing.existing_codes[name]}">
%elif c.user.gold:
<a class="claim-code-button" onclick="r.gold.claim_gold_partner_deal_code(this, '${name}')">${_('claim code')}</a>
%else:
<a class="claim-code-button" href="/gold">${_('buy reddit gold')}</a>
%endif
</%call>
</%def>
<%def name="content()">
<section id="about-gold-partners">
${goldinfo_header(_('reddit gold partners'),
strings.gold_partners_description)}
${partner_item('backblaze',
_("Claim your code below, then visit https://secure.backblaze.com/gift/XXXXXXX (replacing Xs with your code), and click 'Redeem &amp; Download' to install the product and enter your email/password."),
static('gold/partner-backblaze.png'),
_('# Backblaze Online Backup\n'
'3 months of free service from Backblaze Online Backup.'),
'new')}
${goldinfo_footer()}
</section>
</%def>