This repository has been archived by the owner. It is now read-only.
Permalink
Comparing changes
Open a pull request
- 8 commits
- 12 files changed
- 0 commit comments
- 1 contributor
Commits on Oct 09, 2013
Unified
Split
Showing
with
250 additions
and 4 deletions.
- +2 −0 r2/example.ini
- +3 −0 r2/r2/config/routing.py
- +14 −0 r2/r2/controllers/api.py
- +3 −0 r2/r2/lib/app_globals.py
- +92 −3 r2/r2/lib/pages/pages.py
- +23 −0 r2/r2/lib/utils/utils.py
- +1 −0 r2/r2/models/account.py
- +4 −0 r2/r2/models/bidding.py
- +24 −0 r2/r2/models/gold.py
- +29 −0 r2/r2/public/static/css/reddit.less
- +54 −0 r2/r2/templates/serversecondsbar.html
- +1 −1 r2/setup.py
| @@ -611,3 +611,5 @@ listing_chooser_sample_multis = /user/reddit/m/hello, /user/reddit/m/world | ||
| listing_chooser_gold_multi = /user/reddit/m/gold | ||
| # subreddit showcasing new multireddits | ||
| listing_chooser_explore_sr = | ||
| # historical cost to run a reddit server | ||
| pennies_per_server_second = 1970/1/1:1 | ||
| @@ -342,6 +342,9 @@ def make_map(): | ||
| mc('/api/recommend/sr/:srnames', controller='api', | ||
| action='subreddit_recommendations') | ||
|
|
||
| mc('/api/server_seconds_visibility', controller='api', | ||
| action='server_seconds_visibility') | ||
|
|
||
| mc("/api/multi/mine", controller="multiapi", action="my_multis") | ||
| mc("/api/multi/copy", controller="multiapi", action="multi_copy") | ||
| mc("/api/multi/rename", controller="multiapi", action="multi_rename") | ||
| @@ -3616,3 +3616,17 @@ def GET_subreddit_recommendations(self, srs, to_omit): | ||
| to_omit=to_omit.values()) | ||
| sr_data = [{'sr_name': sr.name} for sr in rec_srs] | ||
| return json.dumps(sr_data) | ||
|
|
||
|
|
||
| @validatedForm( | ||
| VUser(), | ||
| VModhash(), | ||
| seconds_visibility=VOneOf( | ||
| "seconds_visibility", | ||
| ("public", "private"), | ||
| default="private", | ||
| ), | ||
| ) | ||
| def POST_server_seconds_visibility(self, form, jquery, seconds_visibility): | ||
| c.user.pref_public_server_seconds = seconds_visibility == "public" | ||
| c.user._commit() | ||
| @@ -247,6 +247,9 @@ class Globals(object): | ||
| 'sidebar_message', | ||
| 'gold_sidebar_message', | ||
| ], | ||
| ConfigValue.dict(ConfigValue.str, ConfigValue.float): [ | ||
| 'pennies_per_server_second', | ||
| ], | ||
| } | ||
|
|
||
| def __init__(self, global_conf, app_conf, paths, **extra): | ||
| @@ -26,10 +26,12 @@ | ||
| from r2.models import Account, FakeAccount, DefaultSR, make_feedurl | ||
| from r2.models import FakeSubreddit, Subreddit, SubSR, AllMinus, AllSR | ||
| from r2.models import Friends, All, Sub, NotFound, DomainSR, Random, Mod, RandomNSFW, RandomSubscription, MultiReddit, ModSR, Frontpage, LabeledMulti | ||
| from r2.models import Link, Printable, Trophy, bidding, PromoCampaign, PromotionWeights, Comment | ||
| from r2.models import Link, Printable, Trophy, 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.bidding import Bid | ||
| from r2.models.gold import gold_payments_by_user, gold_received_by_user | ||
| from r2.models.promo import NO_TRANSACTION, PromotionLog, PromotedLinkRoadblock | ||
| from r2.models.token import OAuth2Client, OAuth2AccessToken | ||
| from r2.models import traffic | ||
| @@ -72,6 +74,7 @@ | ||
| from r2.lib.utils import trunc_string as _truncate, to_date | ||
| from r2.lib.filters import safemarkdown | ||
| from r2.lib.utils import Storage | ||
| from r2.lib.utils import precise_format_timedelta | ||
|
|
||
| from babel.numbers import format_currency | ||
| from collections import defaultdict | ||
| @@ -1603,6 +1606,13 @@ def rightbox(self): | ||
| elif c.user_is_sponsor: | ||
| from admin_pages import SponsorSidebar | ||
| rb.push(SponsorSidebar(self.user)) | ||
|
|
||
| if (c.user == self.user or c.user.employee or | ||
| self.user.pref_public_server_seconds): | ||
| seconds_bar = ServerSecondsBar(self.user) | ||
| if seconds_bar.message or seconds_bar.gift_message: | ||
| rb.push(seconds_bar) | ||
|
|
||
| rb.push(ProfileBar(self.user)) | ||
|
|
||
| return rb | ||
| @@ -1691,6 +1701,85 @@ def __init__(self, user): | ||
| self.my_fullname = c.user._fullname | ||
| self.is_friend = self.user._id in c.user.friends | ||
|
|
||
|
|
||
| class ServerSecondsBar(Templated): | ||
| pennies_per_server_second = { | ||
| datetime.datetime.strptime(datestr, "%Y/%m/%d").date(): v | ||
| for datestr, v in g.live_config['pennies_per_server_second'].iteritems() | ||
| } | ||
|
|
||
| my_message = _("you have helped pay for %(time)s of reddit server time.") | ||
| their_message = _("%(user)s has helped pay for %%(time)s of reddit server " | ||
| "time.") | ||
|
|
||
| my_gift_message = _("gifts on your behalf have helped pay for %(time)s of " | ||
| "reddit server time.") | ||
| their_gift_message = _("gifts on behalf of %(user)s have helped pay for " | ||
| "%%(time)s of reddit server time.") | ||
|
|
||
| @classmethod | ||
| def get_rate(cls, dt): | ||
| cutoff_dates = sorted(cls.pennies_per_server_second.keys()) | ||
| dt = dt.date() | ||
| key = max(filter(lambda cutoff_date: dt >= cutoff_date, cutoff_dates)) | ||
| return cls.pennies_per_server_second[key] | ||
|
|
||
| @classmethod | ||
| def subtract_fees(cls, pennies): | ||
| # for simplicity all payment processor fees are $0.30 + 2.9% | ||
| return pennies * (1 - 0.029) - 30 | ||
|
|
||
| def make_message(self, seconds, my_message, their_message): | ||
| if not seconds: | ||
| return '' | ||
|
|
||
| delta = datetime.timedelta(seconds=seconds) | ||
| server_time = precise_format_timedelta(delta, threshold=5, | ||
| locale=c.locale) | ||
| if c.user == self.user: | ||
| message = my_message | ||
| else: | ||
| message = their_message % {'user': self.user.name} | ||
| return message % {'time': server_time} | ||
|
|
||
| def __init__(self, user): | ||
| Templated.__init__(self) | ||
|
|
||
| self.is_public = user.pref_public_server_seconds | ||
| self.is_user = c.user == user | ||
| self.user = user | ||
|
|
||
| seconds = 0. | ||
| gold_payments = gold_payments_by_user(user) | ||
|
|
||
| for payment in gold_payments: | ||
| rate = self.get_rate(payment.date) | ||
| seconds += self.subtract_fees(payment.pennies) / rate | ||
|
|
||
| try: | ||
| q = (Bid.query().filter(Bid.account_id == user._id) | ||
| .filter(Bid.status == Bid.STATUS.CHARGE) | ||
| .filter(Bid.transaction > 0)) | ||
| selfserve_payments = list(q) | ||
| except NotFound: | ||
| selfserve_payments = [] | ||
|
|
||
| for payment in selfserve_payments: | ||
| rate = self.get_rate(payment.date) | ||
| seconds += self.subtract_fees(payment.charge_amount * 100) / rate | ||
| self.message = self.make_message(seconds, self.my_message, | ||
| self.their_message) | ||
|
|
||
| seconds = 0. | ||
| gold_gifts = gold_received_by_user(user) | ||
|
|
||
| for payment in gold_gifts: | ||
| rate = self.get_rate(payment.date) | ||
| seconds += self.subtract_fees(payment.pennies) / rate | ||
| self.gift_message = self.make_message(seconds, self.my_gift_message, | ||
| self.their_gift_message) | ||
|
|
||
|
|
||
| class MenuArea(Templated): | ||
| """Draws the gray box at the top of a page for sort menus""" | ||
| def __init__(self, menus = []): | ||
| @@ -3337,15 +3426,15 @@ def setup(self, link, listing): | ||
|
|
||
| if c.user_is_sponsor: | ||
| try: | ||
| bids = bidding.Bid.lookup(thing_id=link._id) | ||
| bids = Bid.lookup(thing_id=link._id) | ||
| except NotFound: | ||
| pass | ||
| else: | ||
| bids.sort(key=lambda x: x.date, reverse=True) | ||
| bidders = Account._byID(set(bid.account_id for bid in bids), | ||
| data=True, return_dict=True) | ||
| for bid in bids: | ||
| status = bidding.Bid.STATUS.name[bid.status].lower() | ||
| status = Bid.STATUS.name[bid.status].lower() | ||
| bidder = bidders[bid.account_id] | ||
| row = Storage( | ||
| status=status, | ||
| @@ -26,6 +26,7 @@ | ||
| import ConfigParser | ||
| import codecs | ||
|
|
||
| from babel.dates import TIMEDELTA_UNITS | ||
| from urllib import unquote_plus | ||
| from urllib2 import urlopen, Request | ||
| from urlparse import urlparse, urlunparse | ||
| @@ -1492,3 +1493,25 @@ def canonicalize_email(email): | ||
| localpart = localpart.partition("+")[0] | ||
|
|
||
| return localpart + "@" + domain | ||
|
|
||
|
|
||
| def precise_format_timedelta(delta, locale, threshold=.85, decimals=2): | ||
| """Like babel.dates.format_datetime but with adjustable precision""" | ||
| seconds = delta.total_seconds() | ||
|
|
||
| for unit, secs_per_unit in TIMEDELTA_UNITS: | ||
| value = abs(seconds) / secs_per_unit | ||
| if value >= threshold: | ||
| plural_form = locale.plural_form(value) | ||
| pattern = None | ||
| for choice in (unit + ':medium', unit): | ||
| patterns = locale._data['unit_patterns'].get(choice) | ||
| if patterns is not None: | ||
| pattern = patterns[plural_form] | ||
| break | ||
| if pattern is None: | ||
| return u'' | ||
| decimals = int(decimals) | ||
| format_string = "%." + str(decimals) + "f" | ||
| return pattern.replace('{0}', format_string % value) | ||
| return u'' | ||
| @@ -93,6 +93,7 @@ class Account(Thing): | ||
| pref_highlight_new_comments = True, | ||
| pref_monitor_mentions=True, | ||
| pref_collapse_left_bar=False, | ||
| pref_public_server_seconds=False, | ||
| mobile_compress = False, | ||
| mobile_thumbnail = True, | ||
| trusted_sponsor = False, | ||
| @@ -354,6 +354,10 @@ def refund(self, amount): | ||
| def is_refund(self): | ||
| return (self.status == self.STATUS.REFUND) | ||
|
|
||
| @property | ||
| def charge_amount(self): | ||
| return self.charge or self.bid | ||
|
|
||
|
|
||
| class PromotionWeights(Sessionized, Base): | ||
| __tablename__ = "promotion_weight" | ||
| @@ -461,6 +461,30 @@ def update_gold_transaction(transaction_id, status): | ||
| rp = gold_table.update(gold_table.c.trans_id == str(transaction_id), | ||
| values={gold_table.c.status: status}).execute() | ||
|
|
||
|
|
||
| def transactions_by_user(user): | ||
| s = sa.select([gold_table], gold_table.c.account_id == str(user._id)) | ||
| res = s.execute().fetchall() | ||
| return res | ||
|
|
||
|
|
||
| def gold_payments_by_user(user): | ||
| transactions = transactions_by_user(user) | ||
|
|
||
| # filter out received gifts | ||
| transactions = [trans for trans in transactions | ||
| if not trans.trans_id.startswith(('X', 'M'))] | ||
|
|
||
| return transactions | ||
|
|
||
|
|
||
| def gold_received_by_user(user): | ||
| transactions = transactions_by_user(user) | ||
| transactions = [trans for trans in transactions | ||
| if trans.trans_id.startswith('X')] | ||
| return transactions | ||
|
|
||
|
|
||
| def append_random_bottlecap_phrase(message): | ||
| """Appends a random "bottlecap" phrase from the wiki page. | ||
| @@ -6355,6 +6355,35 @@ body:not(.gold) .allminus-link { | ||
| font-size: 16px; | ||
| } | ||
|
|
||
| .server-seconds { | ||
| background-color: #EFF7FF; | ||
| border: 1px solid #5F99CF; | ||
| text-align: center; | ||
| padding: 10px; | ||
|
|
||
| p { | ||
| padding: 5px 0; | ||
| } | ||
| } | ||
|
|
||
| .server-seconds-public label { | ||
| margin: 0 5px; | ||
| position: relative; | ||
| top: -2px; | ||
| } | ||
|
|
||
| .server-seconds-public.bottom { | ||
| margin-top: 10px; | ||
| padding-top: 10px; | ||
| text-align: left; | ||
| } | ||
|
|
||
| .server-seconds-public .title { | ||
| float: left; | ||
| padding-left: 20px; | ||
| margin-right: 10px; | ||
| } | ||
|
|
||
| .comment-visits-box .title { | ||
| font-weight: bold; | ||
| font-size: 12px; | ||
| @@ -0,0 +1,54 @@ | ||
| ## 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. | ||
| ############################################################################### | ||
|
|
||
| <%namespace file="utils.html" import="inline_radio_type"/> | ||
|
|
||
| <div class="titlebox"> | ||
| <div class="server-seconds rounded"> | ||
| %if thing.message: | ||
| <p>${thing.message}</p> | ||
| %endif | ||
|
|
||
| %if thing.gift_message: | ||
| <p>${thing.gift_message}</p> | ||
| %endif | ||
|
|
||
| %if thing.is_user: | ||
| <div class="server-seconds-public bottom"> | ||
| <div class="title">${_("visible to:")}</div> | ||
| <form id="seconds_visibility_form"> | ||
| ${inline_radio_type("seconds_visibility", "private", _("only me"), checked=not thing.is_public)} | ||
| ${inline_radio_type("seconds_visibility", "public", _("everyone"), checked=thing.is_public)} | ||
| </form> | ||
| </div> | ||
| %endif | ||
| </div> | ||
| </div> | ||
|
|
||
| <script type="text/javascript"> | ||
| $('[name="seconds_visibility"]').click( | ||
| function () { | ||
| var form = $('#seconds_visibility_form'); | ||
| post_form(form, 'server_seconds_visibility'); | ||
| } | ||
| ) | ||
| </script> |
| @@ -74,7 +74,7 @@ | ||
| "boto >= 2.0", | ||
| "pytz", | ||
| "pycrypto", | ||
| "Babel>=0.9.1", | ||
| "Babel>=1.0", | ||
| "cython>=0.14", | ||
| "SQLAlchemy==0.7.4", | ||
| "BeautifulSoup", | ||