Epic Caching Update. Major features (mostly in r2.lib.wrapped)

* Wrapped -> Templated in cases when there is no thing to be wrapped
 * cachable templates out of cachable pieces which can be reused
 * auto-generation of cache keys for cachable content
 * c.render_style used to propagate current style to children
 * cut down on the number of c and g variables in thing templates.
 * buttons in printable.html now in printablebuttons.html with handler classes in r2.lib.pages.things (planned destination of add_props).
This commit is contained in:
KeyserSosa
2009-07-06 14:36:33 -07:00
parent 9a4b45d310
commit 84c1375339
97 changed files with 1674 additions and 1772 deletions

View File

@@ -23,7 +23,7 @@
# Jacascript files to be compressified
js_targets = jquery.js jquery.json.js jquery.reddit.js reddit.js
# CSS targets
css_targets = reddit.css reddit-ie6-hax.css reddit-ie7-hax.css
css_targets = reddit.css reddit-ie6-hax.css reddit-ie7-hax.css mobile.css
SED=sed

View File

@@ -24,6 +24,9 @@ import os
#import pylons.config
from pylons import config
import mimetypes
mimetypes.init()
import webhelpers
from r2.config.routing import make_map

View File

@@ -39,8 +39,7 @@ from r2.lib.jsontemplates import api_type
#middleware stuff
from r2.lib.html_source import HTMLValidationParser
from cStringIO import StringIO
import sys, tempfile, urllib, re, os, sha
import sys, tempfile, urllib, re, os, sha, subprocess
#from pylons.middleware import error_mapper
def error_mapper(code, message, environ, global_conf=None, **kw):
@@ -94,17 +93,70 @@ class DebugMiddleware(object):
if debug and self.keyword in args.keys():
prof_arg = args.get(self.keyword)
prof_arg = urllib.unquote(prof_arg) if prof_arg else None
return self.filter(foo, prof_arg = prof_arg)
r = self.filter(foo, prof_arg = prof_arg)
if isinstance(r, Response):
return r(environ, start_response)
return r
return self.app(environ, start_response)
def filter(self, execution_func, prof_arg = None):
pass
class ProfileGraphMiddleware(DebugMiddleware):
def __init__(self, app):
DebugMiddleware.__init__(self, app, 'profile-graph')
def filter(self, execution_func, prof_arg = None):
# put thie imports here so the app doesn't choke if profiling
# is not present (this is a debug-only feature anyway)
import cProfile as profile
from pstats import Stats
from r2.lib.contrib import gprof2dot
# profiling needs an actual file to dump to. Everything else
# can be mitigated with streams
tmpfile = tempfile.NamedTemporaryFile()
dotfile = StringIO()
# simple cutoff validation
try:
cutoff = .01 if prof_arg is None else float(prof_arg)/100
except ValueError:
cutoff = .01
try:
# profile the code in the current context
profile.runctx('execution_func()',
globals(), locals(), tmpfile.name)
# parse the data
parser = gprof2dot.PstatsParser(tmpfile.name)
prof = parser.parse()
# remove nodes and edges with less than cutoff work
prof.prune(cutoff, cutoff)
# make the dotfile
dot = gprof2dot.DotWriter(dotfile)
dot.graph(prof, gprof2dot.TEMPERATURE_COLORMAP)
# convert the dotfile to PNG in local stdout
proc = subprocess.Popen("dot -Tpng",
shell = True,
stdin =subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, error = proc.communicate(input = dotfile.getvalue())
# generate the response
r = Response()
r.status_code = 200
r.headers['content-type'] = "image/png"
r.content = out
return r
finally:
tmpfile.close()
class ProfilingMiddleware(DebugMiddleware):
def __init__(self, app):
DebugMiddleware.__init__(self, app, 'profile')
def filter(self, execution_func, prof_arg = None):
# put thie imports here so the app doesn't choke if profiling
# is not present (this is a debug-only feature anyway)
import cProfile as profile
from pstats import Stats
@@ -383,6 +435,10 @@ class RequestLogMiddleware(object):
return r
class LimitUploadSize(object):
"""
Middleware for restricting the size of uploaded files (such as
image files for the CSS editing capability).
"""
def __init__(self, app, max_size=1024*500):
self.app = app
self.max_size = max_size
@@ -399,6 +455,29 @@ class LimitUploadSize(object):
return self.app(environ, start_response)
class CleanupMiddleware(object):
"""
Put anything here that should be called after every other bit of
middleware. This currently includes the code for removing
duplicate headers (such as multiple cookie setting). The behavior
here is to disregard all but the last record.
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
def custom_start_response(status, headers, exc_info = None):
fixed = []
seen = set()
for head, val in reversed(headers):
head = head.lower()
if head not in seen:
fixed.insert(0, (head, val))
seen.add(head)
return start_response(status, fixed, exc_info)
return self.app(environ, custom_start_response)
#god this shit is disorganized and confusing
class RedditApp(PylonsBaseWSGIApp):
def find_controller(self, controller):
@@ -439,7 +518,11 @@ def make_app(global_conf, full_stack=True, **app_conf):
# CUSTOM MIDDLEWARE HERE (filtered by the error handling middlewares)
# last thing first from here down
app = CleanupMiddleware(app)
app = LimitUploadSize(app)
app = ProfileGraphMiddleware(app)
app = ProfilingMiddleware(app)
app = SourceViewMiddleware(app)

View File

@@ -29,7 +29,7 @@ def api(type, cls):
tpm.add_handler(type, 'api-html', cls())
# blanket fallback rule
api('wrapped', NullJsonTemplate)
api('templated', NullJsonTemplate)
# class specific overrides
api('link', LinkJsonTemplate)

View File

@@ -30,14 +30,13 @@ from r2.models import *
from r2.models.subreddit import Default as DefaultSR
import r2.models.thing_changes as tc
from r2.controllers import ListingController
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
from r2.lib.utils import query_string, to36, timefromnow, link_from_url
from r2.lib.wrapped import Wrapped
from r2.lib.pages import FriendList, ContributorList, ModList, \
BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage, \
ClickGadget
from r2.lib.pages.things import wrap_links, default_thing_wrapper
from r2.lib.menus import CommentSortMenu
from r2.lib.normalized_hot import expire_hot
@@ -84,8 +83,7 @@ class ApiController(RedditController):
return abort(404, 'not found')
links = link_from_url(request.params.get('url'), filter_spam = False)
builder = IDBuilder([link._fullname for link in links], num = count)
listing = LinkListing(builder, nextprev = False).listing()
listing = wrap_links(links, num = count)
return BoringPage(_("API"), content = listing).render()
@validatedForm(VCaptcha(),
@@ -574,8 +572,7 @@ class ApiController(RedditController):
if kind == 'link':
set_last_modified(item, 'comments')
wrapper = make_wrapper(ListingController.builder_wrapper,
expand_children = True)
wrapper = default_thing_wrapper(expand_children = True)
jquery(".content").replace_things(item, True, True, wrap = wrapper)
@validatedForm(VUser(),
@@ -1116,7 +1113,8 @@ class ApiController(RedditController):
if children:
builder = CommentBuilder(link, CommentSortMenu.operator(sort),
children)
items = builder.get_items(starting_depth = depth, num = 20)
listing = Listing(builder, nextprev = False)
items = listing.get_items(starting_depth = depth, num = 20)
def _children(cur_items):
items = []
for cm in cur_items:
@@ -1286,12 +1284,8 @@ class ApiController(RedditController):
@validatedForm(links = VByName('links', thing_cls = Link, multiple = True),
show = VByName('show', thing_cls = Link, multiple = False))
def POST_fetch_links(self, form, jquery, links, show):
b = IDBuilder([l._fullname for l in links],
wrap = ListingController.builder_wrapper)
l = OrganicListing(b)
l.num_margin = 0
l.mid_margin = 0
l = wrap_links(links, listing_cls = OrganicListing,
num_margin = 0, mid_margin = 0)
jquery(".content").replace_things(l, stubs = True)
if show:
@@ -1473,5 +1467,6 @@ class ApiController(RedditController):
if not link:
abort(404, 'not found')
wrapped = IDBuilder([link._fullname]).get_items()[0][0]
wrapped = wrap_links(link)
wrapped = list(wrapped)[0]
return spaceCompress(websafe(wrapped.link_child.content()))

View File

@@ -22,8 +22,8 @@
from reddit_base import RedditController
from r2.lib.pages import Button, ButtonNoBody, ButtonEmbed, ButtonLite, \
ButtonDemoPanel, WidgetDemoPanel, Bookmarklets, BoringPage, Socialite
from r2.lib.pages.things import wrap_links
from r2.models import *
from r2.lib.strings import Score
from r2.lib.utils import tup, query_string
from pylons import c, request
from validator import *
@@ -39,7 +39,7 @@ class ButtonsController(RedditController):
except ValueError:
return 1
def get_wrapped_link(self, url, link = None):
def get_wrapped_link(self, url, link = None, wrapper = None):
try:
if link:
links = [link]
@@ -51,24 +51,20 @@ class ButtonsController(RedditController):
links = []
if links:
# cache the render style and reset it to html
rs = c.render_style
c.render_style = 'html'
link_builder = IDBuilder([l._fullname for l in links],
wrap=ListingController.builder_wrapper)
# link_listing will be the one-element listing at the top
link_listing = LinkListing(link_builder,
nextprev=False).listing()
# reset the render style
c.render_style = rs
links = link_listing.things
kw = {}
if wrapper:
links = wrap_links(links, wrapper = wrapper)
else:
links = wrap_links(links)
links = list(links)
links = max(links, key = lambda x: x._score) if links else None
if not links and wrapper:
return wrapper(None)
return links
# note: even if _by_url successed or a link was passed in,
# it is possible link_listing.things is empty if the
# link(s) is/are members of a private reddit
# return the link with the highest score (if more than 1)
return max(links, key = lambda x: x._score) if links else None
except:
#we don't want to return 500s in other people's pages.
import traceback
@@ -84,43 +80,31 @@ class ButtonsController(RedditController):
width = VInt('width', 0, 800),
link = VByName('id'))
def GET_button_content(self, url, title, css, vote, newwindow, width, link):
# no buttons on domain listings
if isinstance(c.site, DomainSR):
c.site = Default
return self.redirect(request.path + query_string(request.GET))
l = self.get_wrapped_link(url, link)
if l: url = l.url
#disable css hack
if (css != 'http://blog.wired.com/css/redditsocial.css' and
css != 'http://www.wired.com/css/redditsocial.css'):
css = None
bt = self.buttontype()
if bt == 1:
score_fmt = Score.safepoints
else:
score_fmt = Score.number_only
page_handler = Button
if not vote:
page_handler = ButtonNoBody
wrapper = make_wrapper(Button if vote else ButtonNoBody,
url = url,
target = "_new" if newwindow else "_parent",
title = title, vote = vote, bgcolor = c.bgcolor,
width = width, css = css,
button = self.buttontype())
if newwindow:
target = "_new"
else:
target = "_parent"
res = page_handler(button=bt, css=css,
score_fmt = score_fmt, link = l,
url=url, title=title,
vote = vote, target = target,
bgcolor=c.bgcolor, width=width).render()
l = self.get_wrapped_link(url, link, wrapper)
res = l.render()
c.response.content = spaceCompress(res)
return c.response
@validate(buttontype = VInt('t', 1, 5),
url = VSanitizedUrl("url"),
@@ -138,7 +122,8 @@ class ButtonsController(RedditController):
return c.response
buttontype = buttontype or 1
width, height = ((120, 22), (51, 69), (69, 52), (51, 52), (600, 52))[min(buttontype - 1, 4)]
width, height = ((120, 22), (51, 69), (69, 52),
(51, 52), (600, 52))[min(buttontype - 1, 4)]
if _width: width = _width
if _height: height = _height
@@ -147,32 +132,36 @@ class ButtonsController(RedditController):
height=height,
url = url,
referer = request.referer).render()
# we doing want the JS to be cached!
# we doing want the JS to be cached (it is referer dependent)
c.used_cache = True
return self.sendjs(bjs, callback='', escape=False)
@validate(buttonimage = VInt('i', 0, 14),
title = nop('title'),
url = VSanitizedUrl('url'),
newwindow = VBoolean('newwindow', default = False),
styled = VBoolean('styled', default=True))
def GET_button_lite(self, buttonimage, url, styled, newwindow):
def GET_button_lite(self, buttonimage, title, url, styled, newwindow):
c.render_style = 'js'
c.response_content_type = 'text/javascript; charset=UTF-8'
if not url:
url = request.referer
if newwindow:
target = "_new"
else:
target = "_parent"
# we don't want the JS to be cached if the referer was involved.
c.used_cache = True
l = self.get_wrapped_link(url)
image = 1 if buttonimage is None else buttonimage
def builder_wrapper(thing = None):
kw = {}
if not thing:
kw['url'] = url
kw['title'] = title
return ButtonLite(thing,
image = 1 if buttonimage is None else buttonimage,
target = "_new" if newwindow else "_parent",
styled = styled, **kw)
bjs = ButtonLite(image = image, link = l, url = l.url if l else url,
target = target, styled = styled).render()
# we don't want the JS to be cached!
c.used_cache = True
return self.sendjs(bjs, callback='', escape=False)
bjs = self.get_wrapped_link(url, wrapper = builder_wrapper)
return self.sendjs(bjs.render(), callback='', escape=False)

View File

@@ -25,6 +25,7 @@ from reddit_base import RedditController, base_listing
from r2 import config
from r2.models import *
from r2.lib.pages import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import *
from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url
from r2.lib.utils import query_string, UrlParser, link_from_url, link_duplicates
@@ -513,12 +514,10 @@ class FrontController(RedditController):
if links and len(links) == 1:
return self.redirect(links[0].already_submitted_link)
elif links:
builder = IDBuilder([link._fullname for link in links])
listing = LinkListing(builder, nextprev=False).listing()
infotext = (strings.multiple_submitted
% links[0].resubmit_link())
res = BoringPage(_("seen it"),
content = listing,
content = wrap_links(links),
infotext = infotext).render()
return res

View File

@@ -24,6 +24,7 @@ from validator import *
from r2.models import *
from r2.lib.pages import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import NewMenu, TimeMenu, SortMenu, RecSortMenu, ControversyTimeMenu
from r2.lib.rising import get_rising
from r2.lib.wrapped import Wrapped
@@ -148,17 +149,7 @@ class ListingController(RedditController):
"""Contents of the right box when rendering"""
pass
@staticmethod
def builder_wrapper(thing):
w = Wrapped(thing)
if isinstance(thing, Link):
if thing.promoted:
w = Wrapped(thing)
w.render_class = PromotedLink
w.rowstyle = 'promoted link'
return w
builder_wrapper = staticmethod(default_thing_wrapper())
def GET_listing(self, **env):
return self.build_listing(**env)

View File

@@ -23,6 +23,7 @@ from validator import *
from pylons.i18n import _
from r2.models import *
from r2.lib.pages import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import *
from r2.controllers import ListingController
@@ -40,16 +41,10 @@ class PromoteController(RedditController):
@validate(VSponsor())
def GET_current_promos(self):
current_list = get_promoted()
b = IDBuilder(current_list)
render_list = b.get_items()[0]
render_list = list(wrap_links(get_promoted()))
for x in render_list:
if x.promote_until:
x.promote_expires = timetext(datetime.now(g.tz) - x.promote_until)
page = PromotePage('current_promos',
content = PromotedLinks(render_list))
@@ -65,12 +60,8 @@ class PromoteController(RedditController):
link = VLink('link'))
def GET_edit_promo(self, link):
sr = Subreddit._byID(link.sr_id)
names = [link._fullname]
builder = IDBuilder(names, wrap = ListingController.builder_wrapper)
listing = LinkListing(builder,
show_nums = False, nextprev = False)
rendered = listing.listing().render()
listing = wrap_links(link)
rendered = listing.render()
timedeltatext = ''
if link.promote_until:

View File

@@ -22,6 +22,7 @@
from reddit_base import RedditController
from r2.lib.pages import *
from r2.models import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import CommentSortMenu
from r2.lib.filters import spaceCompress, safemarkdown
from r2.lib.memoize import memoize
@@ -151,26 +152,16 @@ class ToolbarController(RedditController):
if not link.subreddit_slow.can_view(c.user):
abort(403, 'forbidden')
def builder_wrapper(cm):
w = Wrapped(cm)
w.render_class = StarkComment
w.target = "_top"
return w
link_builder = IDBuilder((link._fullname,))
link_listing = LinkListing(link_builder, nextprev=False).listing()
links = link_listing.things[0],
links = list(wrap_links(link))
if not links:
# they aren't allowed to see this link
return self.abort(403, 'forbidden')
link = links[0]
res = FrameToolbar(link = link,
title = link.title,
url = link.url)
wrapper = make_wrapper(render_class = StarkComment,
target = "_top")
b = TopCommentBuilder(link, CommentSortMenu.operator('top'),
wrap = builder_wrapper)
wrap = wrapper)
listing = NestedListing(b, num = 10, # TODO: add config var
parent_name = link._fullname)
@@ -180,7 +171,8 @@ class ToolbarController(RedditController):
md_bar = safemarkdown(raw_bar, target="_top")
res = RedditMin(content=CommentsPanel(link=link, listing=listing.listing(),
res = RedditMin(content=CommentsPanel(link=link,
listing=listing.listing(),
expanded=auto_expand_panel(link),
infobar=md_bar))
@@ -194,15 +186,9 @@ class ToolbarController(RedditController):
link = utils.link_from_url(url, multiple = False)
if link:
link_builder = IDBuilder((link._fullname,))
res = FrameToolbar(link = link_builder.get_items()[0][0],
title = link.title,
url = link.url,
domain = None
if link.is_self
else domain(link.url),
expanded = auto_expand_panel(link))
link = list(wrap_links(link, wrapper = FrameToolbar))
if link:
res = link[0]
else:
res = FrameToolbar(link = None,
title = None,

View File

@@ -24,6 +24,7 @@ from __future__ import with_statement
from r2.models import *
from r2.lib.utils import sanitize_url, domain, randstr
from r2.lib.strings import string_dict
from r2.lib.pages.things import wrap_links
from pylons import g, c
from pylons.i18n import _
@@ -332,38 +333,14 @@ def find_preview_links(sr):
return links
def rendered_link(links, media, compress):
from pylons.controllers.util import abort
from r2.controllers import ListingController
try:
render_style = c.render_style
c.render_style = 'html'
with c.user.safe_set_attr:
c.user.pref_compress = compress
c.user.pref_media = media
b = IDBuilder([l._fullname for l in links],
num = 1, wrap = ListingController.builder_wrapper)
return LinkListing(b, nextprev=False,
show_nums=True).listing().render(style='html')
finally:
c.render_style = render_style
with c.user.safe_set_attr:
c.user.pref_compress = compress
c.user.pref_media = media
links = wrap_links(links, show_nums = True, num = 1)
return links.render(style = "html")
def rendered_comment(comments):
try:
render_style = c.render_style
c.render_style = 'html'
b = IDBuilder([x._fullname for x in comments],
num = 1)
return LinkListing(b, nextprev=False,
show_nums=False).listing().render(style='html')
finally:
c.render_style = render_style
return wrap_links(comments, num = 1).render(style = "html")
class BadImage(Exception): pass

View File

@@ -24,6 +24,7 @@ from pylons import c
import cgi
import urllib
import re
from wrapped import Templated, CacheStub
SC_OFF = "<!-- SC_OFF -->"
SC_ON = "<!-- SC_ON -->"
@@ -75,6 +76,8 @@ class _Unsafe(unicode): pass
def _force_unicode(text):
try:
text = unicode(text, 'utf-8')
except UnicodeDecodeError:
text = unicode(text, 'latin1')
except TypeError:
text = unicode(text)
return text
@@ -91,6 +94,12 @@ def websafe_json(text=""):
def mako_websafe(text = ''):
if text.__class__ == _Unsafe:
return text
elif isinstance(text, Templated):
return _Unsafe(text.render())
elif isinstance(text, CacheStub):
return _Unsafe(text)
elif text is None:
return ""
elif text.__class__ != unicode:
text = _force_unicode(text)
return c_websafe(text)

View File

@@ -21,11 +21,11 @@
################################################################################
from r2.lib.utils import tup
from r2.lib.captcha import get_iden
from r2.lib.wrapped import Wrapped
from r2.lib.wrapped import Wrapped, StringTemplate
from r2.lib.filters import websafe_json
from r2.lib.template_helpers import replace_render
from r2.lib.jsontemplates import get_api_subtype
from r2.lib.base import BaseController
from r2.lib.pages.things import wrap_links
from r2.models import IDBuilder, Listing
import simplejson
@@ -88,16 +88,11 @@ class JsonResponse(object):
"""
function for inserting/replacing things in listings.
"""
listing = None
if isinstance(things, Listing):
listing = things.listing()
things = listing.things
things = tup(things)
if not all(isinstance(t, Wrapped) for t in things):
wrap = kw.pop('wrap', Wrapped)
b = IDBuilder([t._fullname for t in things], wrap)
things = b.get_items()[0]
data = [replace_render(listing, t) for t in things]
things = wrap_links(things, wrapper = wrap)
data = [t.render() for t in things]
if kw:
for d in data:

View File

@@ -20,7 +20,7 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
from utils import to36, tup, iters
from wrapped import Wrapped
from wrapped import Wrapped, StringTemplate, CacheStub, CachedVariable
from mako.template import Template
from r2.lib.filters import spaceCompress, safemarkdown
import time, pytz
@@ -42,11 +42,34 @@ def make_typename(typ):
def make_fullname(typ, _id):
return '%s_%s' % (make_typename(typ), to36(_id))
class ObjectTemplate(StringTemplate):
def __init__(self, d):
self.d = d
def update(self, kw):
def _update(obj):
if isinstance(obj, (str, unicode)):
return spaceCompress(StringTemplate(obj).finalize(kw))
elif isinstance(obj, dict):
return dict((k, _update(v)) for k, v in obj.iteritems())
elif isinstance(obj, (list, tuple)):
return map(_update, obj)
elif isinstance(obj, CacheStub) and kw.has_key(obj.name):
return kw[obj.name]
else:
return obj
res = _update(self.d)
return ObjectTemplate(res)
def finalize(self, kw = {}):
return self.update(kw).d
class JsonTemplate(Template):
def __init__(self): pass
def render(self, thing = None, *a, **kw):
return {}
return ObjectTemplate({})
class TableRowTemplate(JsonTemplate):
def cells(self, thing):
@@ -59,15 +82,15 @@ class TableRowTemplate(JsonTemplate):
return ""
def render(self, thing = None, *a, **kw):
return {"id": self.css_id(thing),
"css_class": self.css_class(thing),
"cells": self.cells(thing)}
return ObjectTemplate(dict(id = self.css_id(thing),
css_class = self.css_class(thing),
cells = self.cells(thing)))
class UserItemJsonTemplate(TableRowTemplate):
def cells(self, thing):
cells = []
for cell in thing.cells:
r = Wrapped.part_render(thing, 'cell_type', cell)
r = Templated.part_render(thing, 'cell_type', cell)
cells.append(spaceCompress(r))
return cells
@@ -90,18 +113,6 @@ class ThingJsonTemplate(JsonTemplate):
d.update(kw)
return d
def points(self, wrapped):
"""
Generates the JS-style point triplet for votable elements
(stored on the vl var on the JS side).
"""
score = wrapped.score
likes = wrapped.likes
base_score = score-1 if likes else score if likes is None else score+1
base_score = [base_score + x for x in range(-1, 2)]
return [wrapped.score_fmt(s) for s in base_score]
def kind(self, wrapped):
"""
Returns a string literal which identifies the type of this
@@ -122,31 +133,18 @@ class ThingJsonTemplate(JsonTemplate):
* id : Thing _fullname of thing.
* content : rendered representation of the thing by
calling replace_render on it using the style of get_api_subtype().
calling render on it using the style of get_api_subtype().
"""
from r2.lib.template_helpers import replace_render
listing = thing.listing if hasattr(thing, "listing") else None
return dict(id = thing._fullname,
content = spaceCompress(
replace_render(listing, thing,
style=get_api_subtype())))
res = dict(id = thing._fullname,
content = thing.render(style=get_api_subtype()))
return res
def raw_data(self, thing):
"""
Complement to rendered_data. Called when a dictionary of
thing data attributes is to be sent across the wire.
"""
def strip_data(x):
if isinstance(x, dict):
return dict((k, strip_data(v)) for k, v in x.iteritems())
elif isinstance(x, iters):
return [strip_data(y) for y in x]
elif isinstance(x, Wrapped):
return x.render()
else:
return x
return dict((k, strip_data(self.thing_attr(thing, v)))
return dict((k, self.thing_attr(thing, v))
for k, v in self._data_attrs_.iteritems())
def thing_attr(self, thing, attr):
@@ -162,7 +160,9 @@ class ThingJsonTemplate(JsonTemplate):
return time.mktime(thing._date.timetuple())
elif attr == "created_utc":
return time.mktime(thing._date.astimezone(pytz.UTC).timetuple())
return getattr(thing, attr) if hasattr(thing, attr) else None
elif attr == "child":
return CachedVariable("childlisting")
return getattr(thing, attr, None)
def data(self, thing):
if get_api_subtype():
@@ -171,7 +171,8 @@ class ThingJsonTemplate(JsonTemplate):
return self.raw_data(thing)
def render(self, thing = None, action = None, *a, **kw):
return dict(kind = self.kind(thing), data = self.data(thing))
return ObjectTemplate(dict(kind = self.kind(thing),
data = self.data(thing)))
class SubredditJsonTemplate(ThingJsonTemplate):
_data_attrs_ = ThingJsonTemplate.data_attrs(subscribers = "score",
@@ -244,26 +245,17 @@ class CommentJsonTemplate(ThingJsonTemplate):
return make_typename(Comment)
def rendered_data(self, wrapped):
from r2.models import Comment, Link
try:
parent_id = wrapped.parent_id
except AttributeError:
parent_id = make_fullname(Link, wrapped.link_id)
else:
parent_id = make_fullname(Comment, parent_id)
d = ThingJsonTemplate.rendered_data(self, wrapped)
d['replies'] = self.thing_attr(wrapped, 'child')
d['contentText'] = self.thing_attr(wrapped, 'body')
d['contentHTML'] = self.thing_attr(wrapped, 'body_html')
d['parent'] = parent_id
d['link'] = make_fullname(Link, wrapped.link_id)
d['link'] = self.thing_attr(wrapped, 'link_id')
d['parent'] = self.thing_attr(wrapped, 'parent_id')
return d
class MoreCommentJsonTemplate(CommentJsonTemplate):
_data_attrs_ = dict(id = "_id36",
name = "_fullname")
def points(self, wrapped):
return []
def kind(self, wrapped):
return "more"
@@ -303,12 +295,14 @@ class MessageJsonTemplate(ThingJsonTemplate):
parent_id = make_fullname(Message, parent_id)
d = ThingJsonTemplate.rendered_data(self, wrapped)
d['parent'] = parent_id
d['contentText'] = self.thing_attr(wrapped, 'body')
d['contentHTML'] = self.thing_attr(wrapped, 'body_html')
return d
class RedditJsonTemplate(JsonTemplate):
def render(self, thing = None, *a, **kw):
return thing.content().render() if thing else {}
return ObjectTemplate(thing.content().render() if thing else {})
class PanestackJsonTemplate(JsonTemplate):
def render(self, thing = None, *a, **kw):
@@ -316,36 +310,34 @@ class PanestackJsonTemplate(JsonTemplate):
res = [x for x in res if x]
if not res:
return {}
return res if len(res) > 1 else res[0]
return ObjectTemplate(res if len(res) > 1 else res[0] )
class NullJsonTemplate(JsonTemplate):
def render(self, thing = None, *a, **kw):
return None
return ""
class ListingJsonTemplate(ThingJsonTemplate):
_data_attrs_ = dict(children = "things")
def points(self, w):
return []
def thing_attr(self, thing, attr):
if attr == "things":
res = []
for a in thing.things:
a.childlisting = False
r = a.render()
if isinstance(r, str):
r = spaceCompress(r)
res.append(r)
return res
return ThingJsonTemplate.thing_attr(self, thing, attr)
def rendered_data(self, thing):
from r2.lib.template_helpers import replace_render
res = []
for a in thing.things:
a.listing = thing
r = replace_render(thing, a, style = 'api')
if isinstance(r, str):
r = spaceCompress(r)
res.append(r)
return res
return self.thing_attr(thing, "things")
def kind(self, wrapped):
return "Listing"
def render(self, *a, **kw):
res = ThingJsonTemplate.render(self, *a, **kw)
return res
class OrganicListingJsonTemplate(ListingJsonTemplate):
def kind(self, wrapped):
return "OrganicListing"
@@ -357,4 +349,4 @@ class TrafficJsonTemplate(JsonTemplate):
if hasattr(thing, ival + "_data"):
res[ival] = [[time.mktime(date.timetuple())] + list(data)
for date, data in getattr(thing, ival+"_data")]
return res
return ObjectTemplate(res)

View File

@@ -19,7 +19,7 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
import pylons
import pylons, sha
from mako.template import Template as mTemplate
from mako.exceptions import TemplateLookupException
from r2.lib.filters import websafe, unsafe
@@ -67,6 +67,11 @@ class tp_manager:
template = _loader.load_template(self.templates[key])
if cache:
self.templates[key] = template
# also store a hash for the template
if (not hasattr(template, "hash") and
hasattr(template, "filename")):
with open(template.filename, 'r') as handle:
template.hash = sha.new(handle.read()).hexdigest()
# cache also for the base class so
# introspection is not required on subsequent passes
if key != top_key:
@@ -78,3 +83,4 @@ class tp_manager:
if not template or not isinstance(template, self.Template):
raise AttributeError, ("template doesn't exist for %s" % str(top_key))
return template

View File

@@ -20,7 +20,7 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
from wrapped import Wrapped, Styled
from wrapped import CachedTemplate, Styled
from pylons import c, request, g
from utils import query_string, timeago
from strings import StringHandler, plurals
@@ -29,6 +29,7 @@ from r2.lib.filters import _force_unicode
from pylons.i18n import _
class MenuHandler(StringHandler):
"""Bastard child of StringHandler and plurals. Menus are
typically a single word (and in some cases, a single plural word
@@ -172,7 +173,7 @@ class NavMenu(Styled):
'style' parameter sets what template/layout to use to differentiate, say,
a dropdown from a flatlist, while the optional _class, and _id attributes
can be used to set individualized CSS."""
def __init__(self, options, default = None, title = '', type = "dropdown",
base_path = '', separator = '|', **kw):
self.options = options
@@ -208,9 +209,6 @@ class NavMenu(Styled):
if opt.dest == self.default:
return opt
def __repr__(self):
return "<NavMenu>"
def __iter__(self):
for opt in self.options:
yield opt
@@ -223,14 +221,17 @@ class NavButton(Styled):
def __init__(self, title, dest, sr_path = True,
nocname=False, opt = '', aliases = [],
target = "", style = "plain", **kw):
# keep original dest to check against c.location when rendering
self.aliases = set(a.rstrip('/') for a in aliases)
self.aliases.add(dest.rstrip('/'))
self.dest = dest
aliases = set(a.rstrip('/') for a in aliases)
aliases.add(dest.rstrip('/'))
self.request_params = dict(request.params)
self.stripped_path = request.path.rstrip('/').lower()
Styled.__init__(self, style = style, sr_path = sr_path,
nocname = nocname, target = target,
nocname = nocname, target = target,
aliases = aliases, dest = dest,
selected = False,
title = title, opt = opt, **kw)
def build(self, base_path = ''):
@@ -239,7 +240,7 @@ class NavButton(Styled):
# append to the path or update the get params dependent on presence
# of opt
if self.opt:
p = request.get.copy()
p = self.request_params.copy()
p[self.opt] = self.dest
else:
p = {}
@@ -258,13 +259,11 @@ class NavButton(Styled):
def is_selected(self):
"""Given the current request path, would the button be selected."""
if self.opt:
return request.params.get(self.opt, '') in self.aliases
return self.request_params.get(self.opt, '') in self.aliases
else:
stripped_path = request.path.rstrip('/').lower()
ustripped_path = _force_unicode(stripped_path)
if stripped_path == self.bare_path:
if self.stripped_path == self.bare_path:
return True
if stripped_path in self.aliases:
if self.stripped_path in self.aliases:
return True
def selected_title(self):
@@ -277,17 +276,24 @@ class OffsiteButton(NavButton):
self.sr_path = False
self.path = self.bare_path = self.dest
def cachable_attrs(self):
return [('path', self.path), ('title', self.title)]
class SubredditButton(NavButton):
def __init__(self, sr):
self.sr = sr
NavButton.__init__(self, sr.name, sr.path, False)
self.path = sr.path
NavButton.__init__(self, sr.name, sr.path, False,
isselected = (c.site == sr))
def build(self, base_path = ''):
self.path = self.sr.path
pass
def is_selected(self):
return c.site == self.sr
return self.isselected
def cachable_attrs(self):
return [('path', self.path), ('title', self.title),
('isselected', self.isselected)]
class NamedButton(NavButton):
"""Convenience class for handling the majority of NavButtons
@@ -345,13 +351,11 @@ class SimpleGetMenu(NavMenu):
type = 'lightdrop'
def __init__(self, **kw):
kw['default'] = kw.get('default', self.default)
kw['base_path'] = kw.get('base_path') or request.path
buttons = [NavButton(self.make_title(n), n, opt = self.get_param)
for n in self.options]
kw['default'] = kw.get('default', self.default)
kw['base_path'] = kw.get('base_path') or request.path
NavMenu.__init__(self, buttons, type = self.type, **kw)
#if kw.get('default'):
# self.selected = kw['default']
def make_title(self, attr):
return menu[attr]
@@ -467,29 +471,29 @@ class NumCommentsMenu(SimpleGetMenu):
def __init__(self, num_comments, **context):
self.num_comments = num_comments
self.max_comments = g.max_comments
self.user_num = c.user.pref_num_comments
SimpleGetMenu.__init__(self, **context)
def make_title(self, attr):
user_num = c.user.pref_num_comments
if user_num > self.num_comments:
if self.user_num > self.num_comments:
# no menus needed if the number of comments is smaller
# than any of the limits
return ""
elif self.num_comments > g.max_comments:
elif self.num_comments > self.max_comments:
# if the number present is larger than the global max,
# label the menu as the user pref and the max number
return dict(true=str(g.max_comments),
false=str(user_num))[attr]
return dict(true=str(self.max_comments),
false=str(self.user_num))[attr]
else:
# if the number is less than the global max, display "all"
# instead for the upper bound.
return dict(true=_("all"),
false=str(user_num))[attr]
false=str(self.user_num))[attr]
def render(self, **kw):
user_num = c.user.pref_num_comments
if user_num > self.num_comments:
if self.user_num > self.num_comments:
return ""
return SimpleGetMenu.render(self, **kw)

View File

@@ -20,18 +20,18 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
from pylons import c, g
from r2.lib.wrapped import Wrapped
from r2.lib.wrapped import Templated
from pages import Reddit
from r2.lib.menus import NamedButton, NavButton, menu, NavMenu
class AdminSidebar(Wrapped):
class AdminSidebar(Templated):
def __init__(self, user):
self.user = user
class Details(Wrapped):
class Details(Templated):
def __init__(self, link):
Wrapped.__init__(self)
Templated.__init__(self)
self.link = link

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ from __future__ import with_statement
import os, re, sys, socket, time, random, time, signal
from itertools import chain
from wrapped import Wrapped
from wrapped import Templated
from datetime import datetime, timedelta
from pylons import g
from r2.lib.utils import tup
@@ -58,7 +58,7 @@ class ShellProcess(object):
return self.output
class AppServiceMonitor(Wrapped):
class AppServiceMonitor(Templated):
cache_key = "service_datalogger_data_"
cache_key_small = "service_datalogger_db_summary_"
cache_lifetime = "memcached_lifetime"
@@ -99,7 +99,7 @@ class AppServiceMonitor(Wrapped):
self._db_info = db_info
self.hostlogs = []
Wrapped.__init__(self)
Templated.__init__(self)
@classmethod
def set_cache_lifetime(cls, data):
@@ -145,7 +145,7 @@ class AppServiceMonitor(Wrapped):
def render(self, *a, **kw):
self.hostlogs = list(self)
return Wrapped.render(self, *a, **kw)
return Templated.render(self, *a, **kw)
def monitor(self, srvname, loop = True, loop_time = 5, *a, **kw):

View File

@@ -213,7 +213,7 @@ class Score(object):
fasion, used primarily by the score() method in printable.html"""
@staticmethod
def number_only(x):
return max(x, 0)
return str(max(x, 0))
@staticmethod
def points(x):

View File

@@ -20,16 +20,16 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
from r2.models import *
from r2.lib.jsontemplates import is_api
from filters import unsafe, websafe
from r2.lib.utils import vote_hash, UrlParser
from r2.lib.utils import vote_hash, UrlParser, timesince
from mako.filters import url_escape
import simplejson
import os.path
from copy import copy
import random
from pylons import i18n, g, c
from pylons import g, c
from pylons.i18n import _, ungettext
def static(file):
"""
@@ -76,7 +76,6 @@ def class_dict():
res = ', '.join(classes)
return unsafe('{ %s }' % res)
def path_info():
loc = dict(path = request.path,
params = dict(request.get))
@@ -84,82 +83,86 @@ def path_info():
return unsafe(simplejson.dumps(loc))
def replace_render(listing, item, style = None, display = True):
style = style or c.render_style or 'html'
rendered_item = item.render(style = style)
# for string rendered items
def string_replace(x, y):
return rendered_item.replace(x, y)
# for JSON responses
def dict_replace(x, y):
try:
res = rendered_item['data']['content']
rendered_item['data']['content'] = res.replace(x, y)
except AttributeError:
pass
except TypeError:
pass
return rendered_item
if is_api():
child_txt = ""
else:
child_txt = ( hasattr(item, "child") and item.child )\
and item.child.render(style = style) or ""
# handle API calls differently from normal request: dicts not strings are passed around
if isinstance(rendered_item, dict):
replace_fn = dict_replace
try:
rendered_item['data']['child'] = child_txt
except AttributeError:
pass
except TypeError:
pass
else:
replace_fn = string_replace
rendered_item = replace_fn(u"$child", child_txt)
#only LinkListing has a show_nums attribute
if listing:
if hasattr(listing, "show_nums"):
if listing.show_nums:
num_str = str(item.num)
if hasattr(listing, "num_margin"):
num_margin = listing.num_margin
else:
num_margin = "%.2fex" % (len(str(listing.max_num))*1.1)
else:
num_str = ''
num_margin = "0px"
def replace_render(listing, item, render_func):
def _replace_render(style = None, display = True):
"""
A helper function for listings to set uncachable attributes on a
rendered thing (item) to its proper display values for the current
context.
"""
style = style or c.render_style or 'html'
replacements = {}
rendered_item = replace_fn(u"$numcolmargin", num_margin)
rendered_item = replace_fn(u"$num", num_str)
if hasattr(listing, "max_score"):
mid_margin = len(str(listing.max_score))
if hasattr(listing, "mid_margin"):
mid_margin = listing.mid_margin
elif mid_margin == 1:
mid_margin = "15px"
child_txt = ( hasattr(item, "child") and item.child )\
and item.child.render(style = style) or ""
replacements["childlisting"] = child_txt
#only LinkListing has a show_nums attribute
if listing:
if hasattr(listing, "show_nums"):
if listing.show_nums:
num_str = str(item.num)
if hasattr(listing, "num_margin"):
num_margin = str(listing.num_margin)
else:
num_margin = "%.2fex" % (len(str(listing.max_num))*1.1)
else:
num_str = ''
num_margin = "0px"
replacements["numcolmargin"] = num_margin
replacements["num"] = num_str
if hasattr(listing, "max_score"):
mid_margin = len(str(listing.max_score))
if hasattr(listing, "mid_margin"):
mid_margin = str(listing.mid_margin)
elif mid_margin == 1:
mid_margin = "15px"
else:
mid_margin = "%dex" % (mid_margin+1)
replacements["midcolmargin"] = mid_margin
#$votehash is only present when voting arrows are present
if c.user_is_loggedin:
replacements['votehash'] = vote_hash(c.user, item,
listing.vote_hash_type)
if hasattr(item, "num_comments"):
if not item.num_comments:
# generates "comment" the imperative verb
com_label = _("comment {verb}")
com_cls = 'comments empty'
else:
mid_margin = "%dex" % (mid_margin+1)
# generates "XX comments" as a noun
com_label = ungettext("comment", "comments", item.num_comments)
com_label = strings.number_label % dict(num=item.num_comments,
thing=com_label)
com_cls = 'comments'
replacements['numcomments'] = com_label
replacements['commentcls'] = com_cls
replacements['display'] = "" if display else "style='display:none'"
if hasattr(item, "render_score"):
# replace the score stub
(replacements['scoredislikes'],
replacements['scoreunvoted'],
replacements['scorelikes']) = item.render_score
# compute the timesince here so we don't end up caching it
if hasattr(item, "_date"):
replacements['timesince'] = timesince(item._date)
rendered_item = replace_fn(u"$midcolmargin", mid_margin)
# TODO: one of these things is not like the other. We should & ->
# $ elsewhere as it plays nicer with the websafe filter.
rendered_item = replace_fn(u"$ListClass", listing._js_cls)
#$votehash is only present when voting arrows are present
if c.user_is_loggedin and u'$votehash' in rendered_item:
hash = vote_hash(c.user, item, listing.vote_hash_type)
rendered_item = replace_fn(u'$votehash', hash)
rendered_item = replace_fn(u"$display", "" if display else "style='display:none'")
return rendered_item
renderer = render_func or item.render
res = renderer(style = style, **replacements)
if isinstance(res, (str, unicode)):
return unsafe(res)
return res
return _replace_render
def get_domain(cname = False, subreddit = True, no_www = False):
"""
@@ -181,15 +184,19 @@ def get_domain(cname = False, subreddit = True, no_www = False):
the trailing path).
"""
# locally cache these lookups as this gets run in a loop in add_props
domain = g.domain
if not no_www and g.domain_prefix:
domain = g.domain_prefix + "." + g.domain
if cname and c.cname and c.site.domain:
domain = c.site.domain
domain_prefix = g.domain_prefix
site = c.site
ccname = c.cname
if not no_www and domain_prefix:
domain = domain_prefix + "." + domain
if cname and ccname and site.domain:
domain = site.domain
if hasattr(request, "port") and request.port:
domain += ":" + str(request.port)
if (not c.cname or not cname) and subreddit:
domain += c.site.path.rstrip('/')
if (not ccname or not cname) and subreddit:
domain += site.path.rstrip('/')
return domain
def dockletStr(context, type, browser):
@@ -241,6 +248,9 @@ def add_sr(path, sr_path = True, nocname=False, force_hostname = False):
* sr_path: if a cname is not used for the domain, updates the
path to include c.site.path.
For caching purposes: note that this function uses:
c.cname, c.render_style, c.site.name
"""
# don't do anything if it is just an anchor
if path.startswith('#') or path.startswith('javascript:'):
@@ -289,7 +299,7 @@ def choose_width(link, width):
if width:
return width - 5
else:
if link:
if hasattr(link, "_ups"):
return 100 + (10 * (len(str(link._ups - link._downs))))
else:
return 110

View File

@@ -27,7 +27,7 @@ from pylons.i18n import _
from babel import Locale
import os, re
import cPickle as pickle
from wrapped import Wrapped
from wrapped import Templated
from utils import Storage
from md5 import md5
@@ -98,7 +98,7 @@ def hax(string):
return hax_dict.get(string, string)
class TranslatedString(Wrapped):
class TranslatedString(Templated):
class _SubstString:
def __init__(self, string, enabled = True):
self.str = hax(string)
@@ -157,7 +157,7 @@ class TranslatedString(Wrapped):
def __init__(self, translator, sing, plural = '', message = '',
enabled = True, locale = '', tip = '', index = 0):
Wrapped.__init__(self)
Templated.__init__(self)
self.translator = translator
self.message = message

View File

@@ -19,29 +19,428 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
from filters import unsafe
from utils import storage
from itertools import chain
import sys
sys.setrecursionlimit(500)
from datetime import datetime
import re, types
class NoTemplateFound(Exception): pass
class Wrapped(object):
class StringTemplate(object):
"""
Simple-minded string templating, where variables of the for $____
in a strinf are replaced with values based on a dictionary.
Unline the built-in Template class, this supports an update method
We could use the built in python Template class for this, but
unfortunately it doesn't handle unicode as gracefully as we'd
like.
"""
start_delim = "<$>"
end_delim = "</$>"
pattern2 = r"[_a-z][_a-z0-9]*"
pattern2 = r"%(start_delim)s(?:(?P<named>%(pattern)s))%(end_delim)s" % \
dict(pattern = pattern2,
start_delim = re.escape(start_delim),
end_delim = re.escape(end_delim),
)
pattern2 = re.compile(pattern2, re.UNICODE)
def __init__(self, template):
# for the nth time, we have to transform the string into
# unicode. Otherwise, re.sub will choke on non-ascii
# characters.
try:
self.template = unicode(template)
except UnicodeDecodeError:
self.template = unicode(template, "utf8")
def __init__(self, *lookups, **context):
self.lookups = lookups
def update(self, d):
"""
Given a dictionary of replacement rules for the Template,
replace variables in the template (once!) and return an
updated Template.
"""
if d:
def convert(m):
name = m.group("named")
return d.get(name, self.start_delim + name + self.end_delim)
return self.__class__(self.pattern2.sub(convert, self.template))
return self
def finalize(self, d = {}):
"""
The same as update, except the dictionary is optional and the
object returned will be a unicode object.
"""
return self.update(d).template
class CacheStub(object):
"""
When using cached renderings, this class generates a stub based on
the hash of the Templated item passed into init for the style
specified.
This class is suitable as a stub object (in the case of API calls)
and wil render in a string form suitable for replacement with
StringTemplate in the case of normal rendering.
"""
def __init__(self, item, style):
self.name = "h%s%s" % (id(item), str(style).replace('-', '_'))
def __str__(self):
return StringTemplate.start_delim + self.name + \
StringTemplate.end_delim
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.name)
class CachedVariable(CacheStub):
"""
Same use as CacheStubs in normal templates, except it can be
applied to where we would normally put a '$____' variable by hand
in a template (file).
"""
def __init__(self, name):
self.name = name
class Templated(object):
"""
Replaces the Wrapped class (which has now a subclass and which
takes an thing to be wrapped).
Templated objects are suitable for rendering and caching, with a
render loop desgined to fetch other cached templates and insert
them into the current template.
"""
# is this template cachable (see CachedTemplate)
cachable = False
# attributes that will not be made into the cache key
cache_ignore = set()
def __repr__(self):
return "<Templated: %s>" % self.__class__.__name__
def __init__(self, **context):
"""
uses context to init __dict__ (making this object a bit like a storage)
"""
for k, v in context.iteritems():
setattr(self, k, v)
if not hasattr(self, "render_class"):
self.render_class = self.__class__
def template(self, style = 'html'):
"""
Fetches template from the template manager
"""
from r2.config.templates import tpm
from pylons import g
debug = g.template_debug
template = None
try:
template = tpm.get(self.render_class,
style, cache = not debug)
except AttributeError:
raise NoTemplateFound, (repr(self), style)
return template
def cache_key(self, *a):
"""
if cachable, this function is used to generate the cache key.
"""
raise NotImplementedError
def render_nocache(self, attr, style):
"""
No-frills (or caching) rendering of the template. The
template is returned as a subclass of StringTemplate and
therefore finalize() must be called on it to turn it into its
final form
"""
from filters import unsafe
from pylons import c
# the style has to default to the global render style
# fetch template
template = self.template(style)
if template:
# store the global render style (since child templates
render_style = c.render_style
c.render_style = style
# are we doing a partial render?
if attr:
template = template.get_def(attr)
# render the template
res = template.render(thing = self)
if not isinstance(res, StringTemplate):
res = StringTemplate(res)
# reset the global render style
c.render_style = render_style
return res
else:
raise NoTemplateFound, repr(self)
def _render(self, attr, style, **kwargs):
"""
Renders the current template with the current style, possibly
doing a part_render if attr is not None.
if this is the first template to be rendered, it is will track
cachable templates, insert stubs for them in the output,
get_multi from the cache, and render the uncached templates.
Uncached but cachable templates are inserted back into the
cache with a set_multi.
NOTE: one of the interesting issues with this function is that
on each newly rendered thing, it is possible that that
rendering has in turn cause more cachable things to be
fetched. Thus the first template to be rendered runs a loop
and keeps rendering until there is nothing left to render.
Then it updates the master template until it doesn't change.
NOTE 2: anything passed in as a kw to render (and thus
_render) will not be part of the cached version of the object,
and will substituted last.
"""
from pylons import c, g
style = style or c.render_style or 'html'
# prepare (and store) the list of cachable items.
primary = False
if not isinstance(c.render_tracker, dict):
primary = True
c.render_tracker = {}
# insert a stub for cachable non-primary templates
if self.cachable:
res = CacheStub(self, style)
cache_key = self.cache_key(attr, style)
# in the tracker, we need to store:
# The render cache key (res.name)
# The memcached cache key(cache_key)
# who I am (self) and what am I doing (attr, style) with what
# (kwargs)
c.render_tracker[res.name] = (cache_key, (self,
(attr, style, kwargs)))
else:
# either a primary template or not cachable, so render it
res = self.render_nocache(attr, style)
# if this is the primary template, let the caching games begin
if primary:
# updates will be the (self-updated) list of all of
# the cached templates that have been cached or
# rendered.
updates = {}
# to_cache is just the keys of the cached templates
# that were not in the cache.
to_cache = set([])
while c.render_tracker:
# copy and wipe the tracker. It'll get repopulated if
# any of the subsequent render()s call cached objects.
current = c.render_tracker
c.render_tracker = {}
# do a multi-get. NOTE: cache keys are the first item
# in the tuple that is the current dict's values.
# This dict cast will generate a new dict of cache_key
# to value
cached = g.rendercache.get_multi(dict(current.values()))
# replacements will be a map of key -> rendered content
# for updateing the current set of updates
replacements = {}
new_updates = {}
# render items that didn't make it into the cached list
for key, (cache_key, others) in current.iteritems():
# unbundle the remaining args
item, (attr, style, kw) = others
if cache_key not in cached:
# this had to be rendered, so cache it later
to_cache.add(cache_key)
# render the item and apply the stored kw args
r = item.render_nocache(attr, style)
else:
r = cached[cache_key]
# store the unevaluated templates in
# cached for caching
replacements[key] = r.finalize(kw)
new_updates[key] = (cache_key, (r, kw))
# update the updates so that when we can do the
# replacement in one pass.
# NOTE: keep kw, but don't update based on them.
# We might have to cache these later, and we want
# to have things like $child present.
for k in updates.keys():
cache_key, (value, kw) = updates[k]
value = value.update(replacements)
updates[k] = cache_key, (value, kw)
updates.update(new_updates)
# at this point, we haven't touched res, but updates now
# has the list of all the updates we could conceivably
# want to make, and to_cache is the list of cache keys
# that we didn't find in the cache.
# cache content that was newly rendered
g.rendercache.set_multi(dict((k, v)
for k, (v, kw) in updates.values()
if k in to_cache))
# edge case: this may be the primary tempalte and cachable
if isinstance(res, CacheStub):
res = updates[res.name][1][0]
# now we can update the updates to make use of their kw args.
updates = dict((k, v.finalize(kw))
for k, (foo, (v, kw)) in updates.iteritems())
# update the response to use these values
# replace till we can't replace any more.
npasses = 0
while True:
npasses += 1
r = res
res = res.update(kwargs).update(updates)
semi_final = res.finalize()
if r.finalize() == res.finalize():
res = semi_final
break
# wipe out the render tracker object
c.render_tracker = None
elif not isinstance(res, CacheStub):
# we're done. Update the template based on the args passed in
res = res.finalize(kwargs)
return res
def render(self, style = None, **kw):
from r2.lib.filters import unsafe
res = self._render(None, style, **kw)
return unsafe(res) if isinstance(res, str) else res
def part_render(self, attr, **kw):
style = kw.get('style')
if style: del kw['style']
return self._render(attr, style, **kw)
class Uncachable(Exception): pass
_easy_cache_cls = set([bool, int, long, float, unicode, str, types.NoneType,
datetime])
def make_cachable(v, *a):
"""
Given an arbitrary object,
"""
if v.__class__ in _easy_cache_cls or isinstance(v, type):
try:
return unicode(v)
except UnicodeDecodeError:
try:
return unicode(v, "utf8")
except (TypeError, UnicodeDecodeError):
return repr(v)
elif isinstance(v, (types.MethodType, CachedVariable) ):
return
elif isinstance(v, (tuple, list, set)):
return repr([make_cachable(x, *a) for x in v])
elif isinstance(v, dict):
return repr(dict((k, make_cachable(v[k], *a))
for k in sorted(v.iterkeys())))
elif hasattr(v, "cache_key"):
return v.cache_key(*a)
else:
raise Uncachable, type(v)
class CachedTemplate(Templated):
cachable = True
def cachable_attrs(self):
"""
Generates an iterator of attr names and their values for every
attr on this element that should be used in generating the cache key.
"""
return ((k, self.__dict__[k]) for k in sorted(self.__dict__)
if (k not in self.cache_ignore and not k.startswith('_')))
def cache_key(self, attr, style, *a):
from pylons import c
# if template debugging is on, there will be no hash and we
# can make the caching process-local.
template_hash = getattr(self.template(style), "hash",
id(self.__class__))
# these values are needed to render any link on the site, and
# a menu is just a set of links, so we best cache against
# them.
keys = [c.user_is_loggedin, c.use_is_admin,
c.render_style, c.cname, c.lang, c.site.name,
template_hash]
keys = [make_cachable(x, *a) for x in keys]
# add all parameters sent into __init__, using their current value
auto_keys = [(k, make_cachable(v, attr, style, *a))
for k, v in self.cachable_attrs()]
# lastly, add anything else that was passed in.
keys.append(repr(auto_keys))
keys.extend(make_cachable(x) for x in a)
return "<%s:[%s]>" % (self.__class__.__name__, u''.join(keys))
class Wrapped(CachedTemplate):
# default to false, evaluate
cachable = False
cache_ignore = set(['lookups'])
def cache_key(self, attr, style):
if self.cachable:
for i, l in enumerate(self.lookups):
if hasattr(l, "wrapped_cache_key"):
# setattr will force a __dict__ entry
setattr(self, "_lookup%d_cache_key" % i,
''.join(map(repr,
l.wrapped_cache_key(self, style))))
return CachedTemplate.cache_key(self, attr, style)
def __init__(self, *lookups, **context):
self.lookups = lookups
# set the default render class to be based on the lookup
if self.__class__ == Wrapped and lookups:
self.render_class = lookups[0].__class__
else:
self.render_class = self.__class__
# this shouldn't be too surprising
self.cache_ignore = self.cache_ignore.union(
set(['cachable', 'render', 'cache_ignore', 'lookups']))
if (not any(hasattr(l, "cachable") for l in lookups) and
any(hasattr(l, "wrapped_cache_key") for l in lookups)):
self.cachable = True
if self.cachable:
for l in lookups:
if hasattr(l, "cache_ignore"):
self.cache_ignore = self.cache_ignore.union(l.cache_ignore)
Templated.__init__(self, **context)
def __repr__(self):
return "<Wrapped: %s, %s>" % (self.__class__.__name__,
self.lookups)
def __getattr__(self, attr):
#print "GETATTR: " + str(attr)
#one would think this would never happen
if attr == 'lookups':
raise AttributeError, attr
@@ -61,59 +460,12 @@ class Wrapped(object):
setattr(self, attr, res)
return res
def __iter__(self):
if self.lookups and hasattr(self.lookups[0], "__iter__"):
return self.lookups[0].__iter__()
raise NotImplementedError
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.lookups)
def template(self, style = 'html'):
from r2.config.templates import tpm
from pylons import g
debug = g.template_debug
template = None
if self.__class__ == Wrapped:
for lookup in chain(self.lookups, (self.render_class,)):
try:
template = tpm.get(lookup, style, cache = not debug)
except AttributeError:
continue
else:
try:
template = tpm.get(self, style, cache = not debug)
except AttributeError:
raise NoTemplateFound, (repr(self), style)
return template
#TODO is this the best way to override style?
def render(self, style = None):
"""Renders the template corresponding to this class in the given style."""
from pylons import c
style = style or c.render_style or 'html'
template = self.template(style)
if template:
res = template.render(thing = self)
return res if (style and style.startswith('api')) else unsafe(res)
else:
raise NoTemplateFound, repr(self)
def part_render(self, attr, *a, **kw):
"""Renders the part of a template associated with the %def
whose name is 'attr'. This is used primarily by
r2.lib.menus.Styled"""
style = kw.get('style', 'html')
template = self.template(style)
dt = template.get_def(attr)
return unsafe(dt.render(thing = self, *a, **kw))
def SimpleWrapped(**kw):
class _SimpleWrapped(Wrapped):
def __init__(self, *a, **kw1):
kw.update(kw1)
Wrapped.__init__(self, *a, **kw)
return _SimpleWrapped
class Styled(Wrapped):
class Styled(CachedTemplate):
"""Rather than creating a separate template for every possible
menu/button style we might want to use, this class overrides the
render function to render only the <%def> in the template whose
@@ -123,15 +475,16 @@ class Styled(Wrapped):
are intended to be used in the outermost container's id and class
tag.
"""
def __init__(self, style, _id = '', css_class = '', **kw):
self._id = _id
self.css_class = css_class
self.style = style
Wrapped.__init__(self, **kw)
CachedTemplate.__init__(self, **kw)
def render(self, **kw):
"""Using the canonical template file, only renders the <%def>
in the template whose name is given by self.style"""
from pylons import c
style = kw.get('style', c.render_style or 'html')
return Wrapped.part_render(self, self.style, style = style, **kw)
return CachedTemplate.part_render(self, self.style, **kw)

View File

@@ -101,7 +101,9 @@ class Builder(object):
for item in items:
w = self.wrap(item)
wrapped.append(w)
# add for caching (plus it should be bad form to use _
# variables in templates)
w.fullname = item._fullname
types.setdefault(w.render_class, []).append(w)
#TODO pull the author stuff into add_props for links and
@@ -124,16 +126,24 @@ class Builder(object):
else:
w.likes = None
#definite
w.timesince = utils.timesince(item._date)
# update vote tallies
compute_votes(w, item)
w.score = w.upvotes - w.downvotes
if w.likes:
base_score = w.score - 1
elif w.likes is None:
base_score = w.score
else:
base_score = w.score + 1
# store the set of available scores based on the vote
# for ease of i18n when there is a label
w.voting_score = [max(base_score + x - 1, 0) for x in range(3)]
w.deleted = item._deleted
w.rowstyle = w.rowstyle if hasattr(w,'rowstyle') else ''
w.rowstyle = getattr(w, 'rowstyle', "")
w.rowstyle += ' ' + ('even' if (count % 2) else 'odd')
count += 1
@@ -157,15 +167,16 @@ class Builder(object):
w.can_ban = True
if item._spam:
w.show_spam = True
if not hasattr(item,'moderator_banned'):
w.moderator_banned = False
w.moderator_banned = getattr(item,'moderator_banned', False)
w.autobanned, w.banner = ban_info.get(item._fullname,
(False, None))
elif hasattr(item,'reported') and item.reported > 0:
w.show_reports = True
# recache the user object: it may be None if user is not logged in,
# whereas now we are happy to have the UnloggedUser object
user = c.user
for cls in types.keys():
cls.add_props(user, types[cls])

View File

@@ -200,47 +200,16 @@ class Link(Thing, Printable):
return True
# none of these things will change over a link's lifetime
cache_ignore = set(['subreddit', 'num_comments', 'link_child']
).union(Printable.cache_ignore)
@staticmethod
def cache_key(wrapped):
if c.user_is_admin:
return False
link_child = wrapped.link_child
s = (str(i) for i in (wrapped._fullname,
bool(c.user_is_sponsor),
bool(c.user_is_loggedin),
wrapped.subreddit == c.site,
c.user.pref_newwindow,
c.user.pref_frame,
c.user.pref_compress,
c.user.pref_media,
request.host,
c.cname,
wrapped.author == c.user,
wrapped.likes,
wrapped.saved,
wrapped.clicked,
wrapped.hidden,
wrapped.friend,
wrapped.show_spam,
wrapped.show_reports,
wrapped.can_ban,
wrapped.thumbnail,
wrapped.moderator_banned,
#link child stuff
bool(link_child),
bool(link_child) and link_child.load,
bool(link_child) and link_child.expand
))
# htmllite depends on other get params
s = ''.join(s)
if c.render_style == "htmllite":
s += ''.join(map(str, [request.get.has_key('style'),
request.get.has_key('expanded'),
request.get.has_key('twocolumn'),
getattr(wrapped, 'embed_voting_style', None),
c.bgcolor,
c.bordercolor]))
def wrapped_cache_key(wrapped, style):
s = Printable.wrapped_cache_key(wrapped, style)
if style == "htmllite":
s.append(request.get.has_key('twocolumn'))
elif style == "xml":
s.append(request.GET.has_key("nothumbs"))
return s
def make_permalink(self, sr, force_domain = False):
@@ -266,23 +235,39 @@ class Link(Thing, Printable):
from r2.lib.media import thumbnail_url
from r2.lib.utils import timeago
from r2.lib.template_helpers import get_domain
from r2.models.subreddit import FakeSubreddit
from r2.lib.wrapped import CachedVariable
saved = Link._saved(user, wrapped) if user else {}
hidden = Link._hidden(user, wrapped) if user else {}
# referencing c's getattr is cheap, but not as cheap when it
# is in a loop that calls it 30 times on 25-200 things.
user_is_admin = c.user_is_admin
user_is_loggedin = c.user_is_loggedin
pref_media = user.pref_media
pref_frame = user.pref_frame
pref_newwindow = user.pref_newwindow
cname = c.cname
site = c.site
saved = Link._saved(user, wrapped) if user_is_loggedin else {}
hidden = Link._hidden(user, wrapped) if user_is_loggedin else {}
#clicked = Link._clicked(user, wrapped) if user else {}
clicked = {}
for item in wrapped:
show_media = False
if c.user.pref_compress:
pass
elif c.user.pref_media == 'on':
if not hasattr(item, "score_fmt"):
item.score_fmt = Score.number_only
item.pref_compress = user.pref_compress
if user.pref_compress:
item.render_css_class = "compressed link"
item.score_fmt = Score.points
elif pref_media == 'on':
show_media = True
elif c.user.pref_media == 'subreddit' and item.subreddit.show_media:
elif pref_media == 'subreddit' and item.subreddit.show_media:
show_media = True
elif (item.promoted
and item.has_thumbnail
and c.user.pref_media != 'off'):
and pref_media != 'off'):
show_media = True
if not show_media:
@@ -292,7 +277,6 @@ class Link(Thing, Printable):
else:
item.thumbnail = g.default_thumb
item.score = max(0, item.score)
item.domain = (domain(item.url) if not item.is_self
@@ -304,23 +288,30 @@ class Link(Thing, Printable):
item.hidden = bool(hidden.get((user, item, 'hide')))
item.clicked = bool(clicked.get((user, item, 'click')))
item.num = None
item.score_fmt = Score.number_only
item.permalink = item.make_permalink(item.subreddit)
if item.is_self:
item.url = item.make_permalink(item.subreddit, force_domain = True)
if c.user_is_admin:
# do we hide the score?
if user_is_admin:
item.hide_score = False
elif item.promoted:
item.hide_score = True
elif c.user == item.author:
elif user == item.author:
item.hide_score = False
elif item._date > timeago("2 hours"):
item.hide_score = True
else:
item.hide_score = False
if c.user_is_loggedin and item.author._id == c.user._id:
# store user preferences locally for caching
item.pref_frame = pref_frame
item.newwindow = pref_newwindow
# is this link a member of a different (non-c.site) subreddit?
item.different_sr = (isinstance(site, FakeSubreddit) or
site.name != item.subreddit.name)
if user_is_loggedin and item.author._id == user._id:
item.nofollow = False
elif item.score <= 1 or item._spam or item.author._spam:
item.nofollow = True
@@ -328,11 +319,11 @@ class Link(Thing, Printable):
item.nofollow = False
item.subreddit_path = item.subreddit.path
if c.cname:
if cname:
item.subreddit_path = ("http://" +
get_domain(cname = (c.site == item.subreddit),
get_domain(cname = (site == item.subreddit),
subreddit = False))
if c.site != item.subreddit:
if site != item.subreddit:
item.subreddit_path += item.subreddit.path
item.domain_path = "/domain/%s" % item.domain
if item.is_self:
@@ -353,7 +344,7 @@ class Link(Thing, Printable):
item.editable = expand and item.author == c.user
item.tblink = "http://%s/tb/%s" % (
get_domain(cname = c.cname, subreddit=False),
get_domain(cname = cname, subreddit=False),
item._id36)
if item.is_self:
@@ -361,7 +352,7 @@ class Link(Thing, Printable):
else:
item.href_url = item.url
if c.user.pref_frame and not item.is_self:
if pref_frame and not item.is_self:
item.mousedown_url = item.tblink
else:
item.mousedown_url = None
@@ -373,9 +364,20 @@ class Link(Thing, Printable):
item._deleted,
item._spam))
if c.user_is_loggedin:
# bits that we will render stubs (to make the cached
# version more flexible)
item.num = CachedVariable("num")
item.numcolmargin = CachedVariable("numcolmargin")
item.commentcls = CachedVariable("commentcls")
item.midcolmargin = CachedVariable("midcolmargin")
item.comment_label = CachedVariable("numcomments")
if user_is_loggedin:
incr_counts(wrapped)
# Run this last
Printable.add_props(user, wrapped)
@property
def subreddit_slow(self):
from subreddit import Subreddit
@@ -395,9 +397,9 @@ class PromotedLink(Link):
@classmethod
def add_props(cls, user, wrapped):
Link.add_props(user, wrapped)
user_is_sponsor = c.user_is_sponsor
try:
if c.user_is_sponsor:
if user_is_sponsor:
promoted_by_ids = set(x.promoted_by
for x in wrapped
if hasattr(x,'promoted_by'))
@@ -414,11 +416,14 @@ class PromotedLink(Link):
for item in wrapped:
# these are potentially paid for placement
item.nofollow = True
item.user_is_sponsor = user_is_sponsor
if item.promoted_by in promoted_by_accounts:
item.promoted_by_name = promoted_by_accounts[item.promoted_by].name
else:
# keep the template from trying to read it
item.promoted_by = None
# Run this last
Printable.add_props(user, wrapped)
class Comment(Thing, Printable):
_data_int_props = Thing._data_int_props + ('reported',)
@@ -478,33 +483,12 @@ class Comment(Thing, Printable):
def keep_item(self, wrapped):
return True
cache_ignore = set(["subreddit", "link", "to"]
).union(Printable.cache_ignore)
@staticmethod
def cache_key(wrapped):
if c.user_is_admin:
return False
s = (str(i) for i in (c.profilepage,
wrapped._fullname,
bool(c.user_is_loggedin),
c.focal_comment == wrapped._id36,
request.host,
c.cname,
wrapped.author == c.user,
wrapped.editted,
wrapped.likes,
wrapped.friend,
wrapped.collapsed,
wrapped.nofollow,
wrapped.show_spam,
wrapped.show_reports,
wrapped.target,
wrapped.can_ban,
wrapped.moderator_banned,
wrapped.can_reply,
wrapped.deleted,
wrapped.render_class,
))
s = ''.join(s)
def wrapped_cache_key(wrapped, style):
s = Printable.wrapped_cache_key(wrapped, style)
s.extend([wrapped.body])
return s
def make_permalink(self, link, sr=None):
@@ -517,6 +501,7 @@ class Comment(Thing, Printable):
@classmethod
def add_props(cls, user, wrapped):
#fetch parent links
links = Link._byID(set(l.link_id for l in wrapped), data = True,
return_dict = True)
@@ -527,13 +512,21 @@ class Comment(Thing, Printable):
subreddits = Subreddit._byID(set(cm.sr_id for cm in wrapped),
data=True,return_dict=False)
can_reply_srs = set(s._id for s in subreddits if s.can_comment(user))
can_reply_srs = set(s._id for s in subreddits if s.can_comment(user)) \
if c.user_is_loggedin else set()
min_score = c.user.pref_min_comment_score
min_score = user.pref_min_comment_score
cids = dict((w._id, w) for w in wrapped)
profilepage = c.profilepage
user_is_admin = c.user_is_admin
user_is_loggedin = c.user_is_loggedin
focal_comment = c.focal_comment
for item in wrapped:
# for caching:
item.profilepage = c.profilepage
item.link = links.get(item.link_id)
if not hasattr(item, 'subreddit'):
@@ -554,32 +547,31 @@ class Comment(Thing, Printable):
# not deleted on profile pages,
# deleted if spam and not author or admin
item.deleted = (not c.profilepage and
item.deleted = (not profilepage and
(item._deleted or
(item._spam and
item.author != c.user and
item.author != user and
not item.show_spam)))
extra_css = ''
if item._deleted:
extra_css += "grayed"
if not c.user_is_admin:
if not user_is_admin:
item.author = DeletedUser()
item.body = '[deleted]'
if c.focal_comment == item._id36:
if focal_comment == item._id36:
extra_css += 'border'
# don't collapse for admins, on profile pages, or if deleted
item.collapsed = ((item.score < min_score) and
not (c.profilepage or
not (profilepage or
item.deleted or
c.user_is_admin))
user_is_admin))
if not hasattr(item,'editted'):
item.editted = False
item.editted = getattr(item, "editted", False)
#score less than 3, nofollow the links
item.nofollow = item._score < 3
@@ -589,32 +581,30 @@ class Comment(Thing, Printable):
item.score_fmt = Score.points
item.permalink = item.make_permalink(item.link, item.subreddit)
item.is_author = (user == item.author)
item.is_focal = (focal_comment == item._id36)
#will seem less horrible when add_props is in pages.py
from r2.lib.pages import UserText
item.usertext = UserText(item, item.body,
editable = item.author == c.user,
editable = item.author == user,
nofollow = item.nofollow,
target = item.target,
extra_css = extra_css)
# Run this last
Printable.add_props(user, wrapped)
class StarkComment(Comment):
"""Render class for the comments in the top-comments display in
the reddit toolbar"""
_nodb = True
class MoreComments(object):
show_spam = False
show_reports = False
is_special = False
can_ban = False
deleted = False
rowstyle = 'even'
reported = False
collapsed = False
author = None
margin = 0
class MoreComments(Printable):
cachable = False
display = ""
@staticmethod
def cache_key(item):
def wrapped_cache_key(item, style):
return False
def __init__(self, link, depth, parent=None):
@@ -647,7 +637,8 @@ class MoreChildren(MoreComments):
class Message(Thing, Printable):
_defaults = dict(reported = 0,)
_data_int_props = Thing._data_int_props + ('reported', )
cache_ignore = set(["to"]).union(Printable.cache_ignore)
@classmethod
def _new(cls, author, to, subject, body, ip, spam = False):
m = Message(subject = subject,
@@ -685,13 +676,15 @@ class Message(Thing, Printable):
else:
item.new = False
item.score_fmt = Score.none
# Run this last
Printable.add_props(user, wrapped)
@staticmethod
def cache_key(wrapped):
#warning: inbox/sent messages
#comments as messages
return False
def wrapped_cache_key(wrapped, style):
s = Printable.wrapped_cache_key(wrapped, style)
s.extend([c.msg_location])
return s
def keep_item(self, wrapped):
return True

View File

@@ -54,38 +54,11 @@ class Listing(object):
def get_items(self, *a, **kw):
"""Wrapper around builder's get_items that caches the rendering."""
from r2.lib.template_helpers import replace_render
builder_items = self.builder.get_items(*a, **kw)
#render cache
#fn to render non-boring items
fullnames = {}
for i in self.builder.item_iter(builder_items):
rs = c.render_style
key = i.render_class.cache_key(i)
if key:
fullnames[key + rs + c.lang] = i
def render_items(names):
r = {}
for i in names:
item = fullnames[i]
r[i] = item.render()
return r
rendered_items = sgm(g.rendercache, fullnames, render_items, 'render_',
time = g.page_cache_time)
#replace the render function
for k, v in rendered_items.iteritems():
def make_fn(v):
default = c.render_style
default_render = fullnames[k].render
def r(style = default):
if style != c.render_style:
return default_render(style = style)
return v
return r
fullnames[k].render = make_fn(v)
for item in self.builder.item_iter(builder_items):
# rewrite the render method
item.render = replace_render(self, item, item.render)
return builder_items
def listing(self):
@@ -104,6 +77,9 @@ class Listing(object):
#TODO: need name for template -- must be better way
return Wrapped(self)
def __iter__(self):
return iter(self.things)
class LinkListing(Listing):
def __init__(self, *a, **kw):
Listing.__init__(self, *a, **kw)

View File

@@ -19,10 +19,52 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
from pylons import c, request
from r2.lib.strings import Score
class Printable(object):
show_spam = False
show_reports = False
is_special = False
can_ban = False
deleted = False
rowstyle = 'even'
reported = False
collapsed = False
author = None
margin = 0
is_focal = False
childlisting = None
cache_ignore = set(['author', 'score_fmt', 'child',
# displayed score is cachable, so remove score
# related fields.
'voting_score', 'display_score',
'render_score', 'score', '_score',
'upvotes', '_ups',
'downvotes', '_downs',
'subreddit_slow',
'cachable', 'make_permalink', 'permalink',
'timesince', 'votehash'
])
@classmethod
def add_props(cls, listing, wrapped):
pass
def add_props(cls, user, wrapped):
from r2.lib.wrapped import CachedVariable
for item in wrapped:
# insert replacement variable for timesince to allow for
# caching of thing templates
item.display = CachedVariable("display")
item.timesince = CachedVariable("timesince")
item.votehash = CachedVariable("votehash")
item.childlisting = CachedVariable("childlisting")
score_fmt = getattr(item, "score_fmt", Score.number_only)
item.display_score = map(score_fmt, item.voting_score)
if item.cachable:
item.render_score = item.display_score
item.display_score = map(CachedVariable,
["scoredislikes", "scoreunvoted",
"scorelikes"])
@property
def permalink(self, *a, **kw):
@@ -30,3 +72,14 @@ class Printable(object):
def keep_item(self, wrapped):
return True
@staticmethod
def wrapped_cache_key(wrapped, style):
s = [wrapped._fullname, wrapped._spam]
if style == 'htmllite':
s.extend([c.bgcolor, c.bordercolor,
request.get.has_key('style'),
request.get.get("expanded"),
getattr(wrapped, 'embed_voting_style', None)])
return s

View File

@@ -248,33 +248,21 @@ class Subreddit(Thing, Printable):
if not user or not user.has_subscribed:
item.subscriber = item._id in defaults
else:
item.subscriber = rels.get((item, user, 'subscriber'))
item.moderator = rels.get((item, user, 'moderator'))
item.contributor = item.moderator or \
rels.get((item, user, 'contributor'))
item.subscriber = bool(rels.get((item, user, 'subscriber')))
item.moderator = bool(rels.get((item, user, 'moderator')))
item.contributor = bool(item.moderator or \
rels.get((item, user, 'contributor')))
item.score = item._ups
item.score_fmt = Score.subscribers
Printable.add_props(user, wrapped)
#TODO: make this work
cache_ignore = set(["subscribers"]).union(Printable.cache_ignore)
@staticmethod
def cache_key(wrapped):
if c.user_is_admin:
return False
s = (str(i) for i in (wrapped._fullname,
bool(c.user_is_loggedin),
wrapped.subscriber,
wrapped.moderator,
wrapped.contributor,
wrapped._spam))
s = ''.join(s)
def wrapped_cache_key(wrapped, style):
s = Printable.wrapped_cache_key(wrapped, style)
s.extend([wrapped._spam])
return s
#TODO: make this work
#@property
#def author_id(self):
#return 1
@classmethod
def top_lang_srs(cls, lang, limit):
"""Returns the default list of subreddits for a given language, sorted

View File

@@ -740,7 +740,6 @@ a.star { text-decoration: none; color: #ff8b60 }
.likes div.score.likes {display: block;}
.dislikes div.score.dislikes {display: block;}
.warm-entry .rank { color: #EDA179; }
.hot-entry .rank { color: #E47234; }
.cool-entry .rank { color: #A5ABFB; }

View File

@@ -281,6 +281,7 @@ function share(elem) {
$(elem).new_thing_child($(".sharelink:first").clone(true)
.attr("id", "sharelink_" + $(elem).thing_id()),
false);
$.request("new_captcha");
};
function cancelShare(elem) {
@@ -888,7 +889,6 @@ function reply(elem) {
form.find(".cancel").get(0).onclick = function() {form.hide()};
}
function populate_click_gadget() {
/* if we can find the click-gadget, populate it */
if($('.click-gadget').length) {

View File

@@ -1,135 +0,0 @@
body {
font-family: verdana,arial,helvetica,sans-serif;
margin: 0;
padding: 0;
font-size: x-small;
color: #888;
}
a {
text-decoration: none;
color: #369;
}
p {
margin: 0px;
padding: 0px;
}
ul {
margin: 0px;
padding: 0px;
list-style: none;
}
.link {
margin-left: 2px;
}
.title {
color: blue;
font-size: small;
margin-right: .5em;
}
.byline {
margin: 0px 0px .5em 2px;
}
.description {
margin-bottom: .5em;
}
.domain {
color: #369;
}
.buttons {
font-weight: bold;
}
.child {
margin-left: 2em;
}
.headerbar {
background:lightgray none repeat scroll 0%;
margin: 5px 0px 5px 2px;
}
.headerbar span {
background-color: white;
color: gray;
font-size: x-small;
font-weight: bold;
margin-left: 15px;
padding: 0px 3px;
}
.score {
margin: 0px .5em 0px .5em;
}
.error {
color: red;
margin: 5px;
}
.nextprev {
margin: 10px;
}
.tabmenu {
list-style-type: none;
}
.tabmenu li {
display: inline;
margin-right: .25em;
padding-left: .25em;
border-left: thin solid #000;
}
.redditname {
font-weight: bold;
margin: 0px 3px 0px 3px;
}
.selected {
font-weight: bold;
}
.pagename {
font-weight: bold;
margin-right: 1ex;
color: black;
}
.or {
border-left:thin solid #000000;
border-right:thin solid #000000;
padding: 0px .25em 0px .25em;
}
#header {
background-color: #CEE3F8;
}
#header .redditname a{
color: black;
}
#header img {
margin-right: 3px;
}
/* markdown */
.md { max-width: 60em; overflow: auto; }
.md p, .md h1 { margin-bottom: .5em;}
.md h1 { font-weight: normal; font-size: 100%; }
.md > * { margin-bottom: 0px }
.md strong { font-weight: bold; }
.md em { font-style: italic; }
.md img { display: none }
.md ol, .md ul { margin: 10px 2em; }
.md pre { margin: 10px; }

View File

@@ -25,5 +25,5 @@
%>
%if c.user_is_admin:
${AppServiceMonitor().render()}
${AppServiceMonitor()}
%endif

View File

@@ -21,7 +21,7 @@
################################################################################
<%namespace file="translation.html" import="statusbar"/>
<%namespace file="printable.html" import="ynbutton, state_button" />
<%namespace file="printablebuttons.html" import="ynbutton, state_button" />
<%
from r2.lib.translation import Translator
%>

View File

@@ -45,7 +45,7 @@
<%def name="bodyHTML()">
<body style="background-color: ${'#%s' % (c.bgcolor or 'FFFFFF')}"
class="button-body">
<div class="button ${thing_css_class(thing.link) if thing.link else ''}">
<div class="button ${thing_css_class(thing) if thing._fullname else ''}">
%if not c.user_is_loggedin:
<div id="cover" style="display: none">
<div class="cover"></div>

View File

@@ -27,11 +27,16 @@
<%namespace file="buttontypes.html" import="submiturl" />
<%
domain = get_domain()
domain = get_domain()
if thing._fullname:
path = thing.make_permalink_slow()
else:
path = capture(submiturl, thing.url, thing.title)
%>
(function() {
var styled_submit = '<a style="color: #369; text-decoration: none;" href="${submiturl(thing.url)}" target="${thing.target}">';
var unstyled_submit = '<a href="${submiturl(thing.url)}" target="${thing.target}">';
var styled_submit = '<a style="color: #369; text-decoration: none;" href="${path}" target="${thing.target}">';
var unstyled_submit = '<a href="${submiturl(thing.url)}" target="${path}">';
var write_string='<span class="reddit_button" style="';
%if thing.styled:
write_string += 'color: grey;';
@@ -40,8 +45,8 @@
%if thing.image > 0:
write_string += unstyled_submit + '<img style="height: 2.3ex; vertical-align:top; margin-right: 1ex" src="http://${get_domain(subreddit=False)}/static/spreddit${thing.image}.gif">' + "</a>";
%endif
%if thing.link:
write_string += '${Score.safepoints(thing.link.score)}';
%if thing._fullname:
write_string += '${Score.safepoints(thing.score)}';
%if thing.styled:
write_string += ' on ' + styled_submit + 'reddit</a>';
%else:

View File

@@ -22,6 +22,7 @@
<%!
from r2.lib.template_helpers import get_domain, static, style_line, choose_width
from r2.lib.utils import query_string
from r2.lib.strings import Score
%>
<%namespace file="printable.html" import="arrow, score" />
@@ -36,9 +37,9 @@
<%def name="class_def(class_number, width=None)">
<%
like_cls = "unvoted"
if getattr(thing.link, "likes", None):
if getattr(thing, "likes", None):
like_cls = "likes"
elif getattr(thing.link, "likes", None) is False:
elif getattr(thing, "likes", None) is False:
like_cls = "dislikes"
%>
<div class="blog blog${class_number} entry ${like_cls}"
@@ -46,41 +47,46 @@
style="${style_line(width)}"
%endif
>
${caller.body()}
</div>
</%def>
<%def name="button1(thing)">
${class_def(1, width=choose_width(thing.link, thing.width))}
<%self:class_def class_number="1"
width="${choose_width(thing, thing.width)}">
<div class="headimgcell">
<a href="${submiturl(thing.url, thing.title)}" target="${thing.target}">
<img src="http://www.reddit.com/static/blog_head.png" alt=""/>
</a>
</div>
%if thing.link:
%if thing._fullname:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
${arrow(thing.link, 0, thing.likes == False)}
${score(thing.link, thing.likes, tag='div')}
${arrow(thing, 1, thing.likes)}
${arrow(thing, 0, thing.likes == False)}
${score(thing, thing.likes,
scores = map(Score.safepoints, thing.voting_score),
tag='div')}
%else:
${thing.link.score}
${thing.score}
%endif
%else:
${submitlink(thing.url, thing.title)}
%endif
<div class="clear"></div>
</div>
</%self:class_def>
</%def>
<%def name="button2(thing)">
${class_def(2)}
%if thing.link:
<%self:class_def class_number="2">
%if thing._fullname:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
${arrow(thing, 1, thing.likes)}
${score(thing, thing.likes, tag='div')}
${arrow(thing, 0, thing.likes == False)}
%else:
&nbsp;<br />
${thing.link.score}
&nbsp;<br />
&nbsp;<br />
${thing.score}
&nbsp;<br />
%endif
%else:
<img src="http://www.reddit.com/static/blog_head.png" alt=""/>
@@ -93,21 +99,21 @@ ${class_def(1, width=choose_width(thing.link, thing.width))}
%endif
<a href="${submiturl(thing.url, thing.title)}" target="${thing.target}">reddit</a>
</div>
</div>
</%self:class_def>
</%def>
<%def name="button3(thing)">
${class_def(3)}
<%self:class_def class_number="3">
<div class="left">
%if thing.link:
%if thing._fullname:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
${arrow(thing, 1, thing.likes)}
${score(thing, thing.likes, tag='div')}
${arrow(thing, 0, thing.likes == False)}
%else:
&nbsp;<br />
${thing.link.score}
${thing.score}
&nbsp;<br />
%endif
%else:
@@ -120,47 +126,48 @@ ${class_def(3)}
${img_link('submit', '/static/blog_snoo.gif', capture(submiturl, thing.url, thing.title), target=thing.target)}
</div>
<div class="clear"></div>
</div>
</%self:class_def>
</%def>
<%def name="button4(thing)">
${class_def(2)}
%if thing.link:
<%self:class_def class_number="2">
%if thing._fullname:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
${arrow(thing, 1, thing.likes)}
${score(thing, thing.likes, tag='div')}
${arrow(thing, 0, thing.likes == False)}
%else:
&nbsp;<br />
${thing.link.score}
${thing.score}
&nbsp;<br />
%endif
%else:
<img src="http://www.reddit.com/static/blog_head.png" alt=""/>
${submitlink(thing.url, thing.title, 'submit to')}
%endif
</div>
</%self:class_def>
</%def>
<%def name="button5(thing)">
${class_def(5, width=choose_width(thing.link, thing.width))}
<%self:class_def class_number="5"
width="${choose_width(thing, thing.width)}">
${img_link('submit', '/static/blog_snoo.gif', capture(submiturl, thing.url, thing.title), _class="left")}
<%
# this button style submits to the cname as a default
c.cname = hasattr(c.site, "domain") and bool(c.site.domain)
submitlink = capture(submiturl, thing.url, thing.title)
if thing.link:
fullname = thing.link._fullname
ups = thing.link.upvotes
downs = thing.link.downvotes
if thing._fullname:
fullname = thing._fullname
ups = thing.upvotes
downs = thing.downvotes
link = "http://%s%s" % (get_domain(cname = c.cname, subreddit = False),
thing.link.make_permalink_slow())
thing.make_permalink_slow())
else:
fullname = ""
ups = 0
downs = 0
link = submitlink
if c.user_is_loggedin:
if c.user_is_loggedin and thing._fullname:
dir = 1 if thing.likes else 0 if thing.likes is None else -1
ups = ups - (1 if dir > 0 else 0)
downs = downs - (1 if dir < 0 else 0)
@@ -206,7 +213,7 @@ ${class_def(3)}
</li>
<li>
<a href="${link}" target="_new">
%if thing.link:
%if thing._fullname:
${_("Discuss at the %(name)s reddit") % dict(name = c.site.name)}
%else:
${_("Submit to the %(name)s reddit") % dict(name = c.site.name)}
@@ -214,7 +221,7 @@ ${class_def(3)}
</a>
</li>
</ul>
<div class="votes ${'' if thing.link else 'disabled'}">
<div class="votes ${'' if thing._fullname else 'disabled'}">
%for i in range(3):
<div class="right" id="controversy${i}_${fullname}"
${"" if i == dir + 1 else "style='display:none'"}>
@@ -223,7 +230,7 @@ ${class_def(3)}
%endfor
<a class="arrow up${'mod' if dir==1 else ''}"
id="up_${fullname}"
%if c.user_is_loggedin and thing.link:
%if c.user_is_loggedin and thing._fullname:
href="#" onclick="$(this).vote('', set_score); return false; "
%else:
href="${submitlink}" target="_new"
@@ -236,7 +243,7 @@ ${class_def(3)}
%endfor
</a>
<a class="arrow down${'mod' if dir==-1 else ''}" id="down_${fullname}"
%if c.user_is_loggedin and thing.link:
%if c.user_is_loggedin and thing._fullname:
href="#" onclick="$(this).vote('', set_score); return false;"
%else:
href="${submitlink}" target="_new"
@@ -251,5 +258,5 @@ ${class_def(3)}
</div>
<div class="clearleft"><!--IEsux--></div>
</div>
</div>
</%self:class_def>
</%def>

View File

@@ -19,14 +19,14 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="printable.html" import="simple_button" />
<%namespace file="printablebuttons.html" import="simple_button" />
%if c.user.pref_clickgadget:
<div class="gadget" style="${'display: none' if not thing.content else ''}">
<h2>${_("Recently viewed links")}</h2>
<div class="click-gadget">
${thing.content}
${unsafe(thing.content)}
</div>
<div class="right">

View File

@@ -22,7 +22,7 @@
<%!
from r2.lib.filters import edit_comment_filter, unsafe, safemarkdown
from r2.lib.utils import to36
from r2.lib.pages.things import CommentButtons
%>
<%inherit file="comment_skeleton.html"/>
@@ -76,7 +76,8 @@ ${parent.collapsed()}
%if show:
${unsafe(self.score(thing, likes = thing.likes))}&#32;
%endif
${_("%(timeago)s ago") % dict(timeago=thing.timesince)}
## thing.timesince is a cache stub
${unsafe(_("%(timeago)s ago") % dict(timeago=thing.timesince))}
%if thing.editted:
<em>*</em>&nbsp;
%endif
@@ -106,41 +107,11 @@ ${parent.Child(not thing.collapsed)}
</%def>
<%def name="arrows()">
${parent.midcol()}
${parent.midcol()}
</%def>
<%def name="buttons()">
<li class="first">
${parent.bylink_button(_("permalink"), thing.permalink)}
</li>
%if thing.deleted:
%if thing.parent_permalink and not c.profilepage:
<li>
${parent.bylink_button(_("parent"), thing.parent_permalink)}
</li>
%endif
%else:
%if not c.profilepage:
%if thing.parent_permalink:
<li>
${parent.bylink_button(_("parent"), thing.parent_permalink)}
</li>
%endif
%if thing.usertext.editable:
<li>
${parent.simple_button(_("edit"), "edit_usertext")}
</li>
%endif
%endif
${parent.delete_or_report_buttons()}
${parent.buttons()}
%if not c.profilepage and thing.can_reply:
<li>
${parent.simple_button(_("reply"), "reply")}
</li>
%endif
%endif
${CommentButtons(thing)}
${self.admintagline()}
</%def>

View File

@@ -28,7 +28,7 @@
<%inherit file="printable.htmllite" />
<%def name="parent()">
%if isBlog:
%if c.profilepage:
<small>
<a href="${thing.ancestor.url}">${thing.ancestor.titleprefix}${thing.ancestor.title}</a></small><br \>
%endif

View File

@@ -27,7 +27,7 @@
<%inherit file="printable.mobile" />
<%def name="parent()">
%if isBlog:
%if c.profilepage:
<small>
<a href="${thing.ancestor.url}">${thing.ancestor.titleprefix}${thing.ancestor.title}</a></small><br \>
%endif

View File

@@ -41,4 +41,4 @@
<description>${body|h}</description>
</item>
${hasattr(thing, "child") and thing.child.render() or ''}
${hasattr(thing, "child") and thing.child or ''}

View File

@@ -24,9 +24,9 @@
<%namespace file="utils.html" import="plain_link" />
<%def name="midcol(display=True, cls = '')">
%if c.profilepage or (not thing._deleted and (not thing._spam or c.user == thing.author or c.user_is_admin)):
${parent.midcol(display=display, cls = cls)}
%endif
%if c.profilepage or (not thing._deleted and (not thing._spam or thing.is_author or c.user_is_admin)):
${parent.midcol(display=display, cls = cls)}
%endif
</%def>
<%def name="tagline(collapse=False)">
@@ -37,7 +37,7 @@ ${self.tagline(True)}
</%def>
<%def name="commentBody()">
${thing.usertext.render(style="html")}
${thing.usertext}
</%def>
@@ -56,7 +56,3 @@ ${self.tagline(True)}
</ul>
</div>
</%def>
<%def name="bylink_button(title, link)">
${plain_link(title, link, _class="bylink", rel="nofollow")}
</%def>

View File

@@ -20,7 +20,7 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="printable.html" import="toggleable_label"/>
<%namespace file="printablebuttons.html" import="toggleable_label"/>
<div class="comments-panel">
@@ -42,6 +42,6 @@
reverse = not c.user.pref_frame_commentspanel)}
</div>
${thing.listing.render()}
${thing.listing}
</div>

View File

@@ -58,15 +58,13 @@
</div>
<div class="spacer">
<%utils:round_field title="${_('message')}">
${UserText(None, have_form = False, creating = True).render()}
<%utils:round_field title="message">
${UserText(None, have_form = False, creating = True)}
</%utils:round_field>
</div>
<div class="spacer">
%if thing.captcha:
${thing.captcha.render()}
%endif
${thing.captcha}
</div>
<button class="btn" type="submit">${_("send")}</button>

View File

@@ -26,7 +26,8 @@
<%inherit file="reddit.html"/>
<%namespace file="utils.html" import="plain_link, logout"/>
<%namespace file="printable.html" import="state_button, comment_button, thing_css_class, score" />
<%namespace file="printablebuttons.html" import="state_button, comment_button"/>
<%namespace file="printable.html" import="thing_css_class, score" />
<%def name="javascript_run()">
${parent.javascript_run()}
@@ -56,7 +57,7 @@
%endif
</a>
%if thing.link:
%if thing._fullname:
${withlink()}
%endif
</div>
@@ -89,7 +90,7 @@
<a href="http://${get_domain(cname = c.cname)}/user/${c.user.name}"
title="${_('visit your userpage')}"
class="clickable" target="_top">${c.user.name}</a>
%elif thing.link:
%elif thing._fullname:
<a href="${thing.loginurl}" target="_top" class="clickable">
${_("login / register")}
</a>
@@ -118,14 +119,14 @@
</div>
<div class="middle-side clickable">
%if thing.link:
%if thing._fullname:
<a
title="${_('click to visit the main page for this submission')}"
class="title clickable"
target="_top"
href="${thing.link.make_permalink_slow()}"
href="${thing.permalink}"
>
${thing.link.title}
${thing.title}
%if thing.domain:
<span class="domain">&#32;(${thing.domain})</span>
@@ -156,15 +157,15 @@
<%def name="withlink()">
<span class="${thing_css_class(thing.link)}">
<span class="${thing_css_class(thing)}">
## add us to the click cookie
<script type="text/javascript">
$(function() {
add_thing_id_to_cookie("${thing.link._fullname}", "recentclicks2");
add_thing_id_to_cookie("${thing._fullname}", "recentclicks2");
});
</script>
<span>
${score(thing.link, thing.link.likes, score_fmt = Score.safepoints, tag='b')}
<span class="entry ${'likes' if thing.likes else 'dislikes' if thing.likes is False else 'unvoted'}">
${score(thing, thing.likes, tag='b')}
</span>
<span class="arrows">
<%def name="arrow(direction, style, message)">
@@ -172,7 +173,7 @@
title="${_('vote %(direction)s') % dict(direction=direction)}"
%if c.user_is_loggedin:
href="javascript:void(0);"
onclick="$(this).vote('${thing.vh}')"
onclick="$(this).vote('${thing.votehash}')"
%else:
href="${thing.loginurl}"
target="_top"
@@ -189,7 +190,7 @@
%if c.user_is_loggedin:
%if thing.link.saved:
%if thing.saved:
${state_button("unsave", _("unsave"),
"return change_state(this, 'unsave');", "<b>%s</b>" % _("unsaved"),
a_class="clickable")}
@@ -200,13 +201,13 @@
%endif
%endif
<a href="/toolbar/comments/${thing.link._id36}" target="reddit_panel"
<a href="/toolbar/comments/${thing._id36}" target="reddit_panel"
title="${_('toggle the comments panel')}"
onclick="toolbar.comments_pushed(this); return toolbar.panel_loadurl(this.href);"
class="comments comments-button">${thing.com_label}</a>
class="comments comments-button">${thing.comment_label}</a>
</span>
%if thing.link and thing.expanded:
%if thing._fullname and thing.expanded:
<script type="text/javascript">
$(function() {
toolbar.comments_pushed($('.comments-button'));

View File

@@ -21,21 +21,21 @@
################################################################################
<%!
from r2.models.subreddit import Default
from r2.lib.template_helpers import get_domain
%>
from r2.lib.pages.things import LinkButtons
%>
<%inherit file="printable.html"/>
<%namespace file="utils.html" import="plain_link" />
<%namespace file="printablebuttons.html" import="toggle_button" />
<%def name="numcol()">
<% num = thing.num %>
<span class="rank" style="width:$numcolmargin;">
<span class="rank" style="width:${thing.numcolmargin};">
%if thing.top_link:
<a class="star" href="/toplinks">
%endif
##placeholder for the number
$num
${thing.num}
%if thing.top_link:
</a>
%endif
@@ -48,7 +48,7 @@
%if thing.nofollow:
rel="nofollow"
%endif
%if c.user.pref_newwindow:
%if thing.newwindow:
target="_blank"
%elif c.cname:
target="_top"
@@ -131,14 +131,14 @@
</%def>
<%def name="midcol(display=True, cls = '')">
%if c.user.pref_compress:
<div class="midcol ${cls}" style="width:2ex;"
%if thing.pref_compress:
<div class="midcol ${cls}" style="width:2ex;"
%else:
<div class="midcol ${cls}" style="width:$midcolmargin;"
<div class="midcol ${cls}" style="width:${thing.midcolmargin};"
%endif
${not display and "style='display:none'" or ''}>
${self.arrow(thing, 1, thing.likes)}
%if c.user.pref_compress:
%if thing.pref_compress:
<div class="score-placeholder"></div>
%elif thing.hide_score:
<div class="score likes">&bull;</div>
@@ -161,19 +161,15 @@
<%def name="tagline()">
<%
from r2.lib.utils import timeago
from r2.models import FakeSubreddit
from r2.lib.strings import Score
if isinstance(c.site, FakeSubreddit) or c.site != thing.subreddit:
if thing.different_sr:
taglinetext = _("submitted %(when)s ago by %(author)s to %(reddit)s")
else:
taglinetext = _("submitted %(when)s ago by %(author)s")
taglinetext = taglinetext.replace(" ", "&#32;")
%>
%if c.user.pref_compress and not thing.hide_score:
${self.score(thing, thing.likes, score_fmt = Score.safepoints, tag='span')}
%if thing.pref_compress and not thing.hide_score:
${self.score(thing, thing.likes, tag='span')}
&#32;
%endif
@@ -185,54 +181,9 @@
<%def name="child()">
</%def>
<%def name="buttons(comments=True,delete=True,report=True,ban=True,additional='')">
<% fullname = thing._fullname %>
%if comments:
<%
if not thing.num_comments:
# generates "comment" the imperative verb
com_label = _("comment {verb}")
else:
# generates "XX comments" as a noun
com_label = ungettext("comment", "comments", thing.num_comments)
%>
<li class="first">
${parent.comment_button("comment", com_label,
thing.num_comments, thing.permalink,
newwindow = c.user.pref_newwindow)}
</li>
%endif
%if thing.editable:
<li>
${parent.simple_button(_("edit"), "edit_usertext")}
</li>
%endif
<li class="share">
${parent.toggle_button("share-button", _("share"), _("cancel"),
"share", "cancelShare", login_required = True)}
</li>
%if c.user_is_loggedin:
<li>
%if thing.saved:
${parent.state_button("unsave", _("unsave"), \
"return change_state(this, 'unsave', unsave_thing);", _("unsaved"))}
%else:
${parent.state_button("save", _("save"), \
"return change_state(this, 'save', save_thing);", _("saved"))}
%endif
</li><li>
%if thing.hidden:
${parent.state_button("unhide", _("unhide"), \
"change_state(this, 'unhide', hide_thing);", _("unhidden"))}
%else:
${parent.state_button("hide", _("hide"), \
"change_state(this, 'hide', hide_thing);", _("hidden"))}
%endif
</li>
%endif
${parent.delete_or_report_buttons(delete=delete,report=report)}
${parent.buttons(ban=ban)}
${additional}
<%def name="buttons(comments=True, delete=True, report=True, ban=True, additional='')">
${LinkButtons(thing, comments = comments, delete = delete,
report = report, ban = ban)}
</%def>
<%def name="thumbnail()">

View File

@@ -31,21 +31,10 @@
<%def name="entry()">
<%
from r2.lib.strings import Score
if thing.num_comments:
# generates "XX comments" as a noun
com_label = "%d %s" % \
(thing.num_comments,
ungettext("comment", "comments", thing.num_comments))
else:
# generates "comment" the imperative verb
com_label = _("comment")
domain = get_domain(subreddit=False)
permalink = "http://%s%s" % (domain, thing.permalink)
expanded = request.get.get("expanded")
two_col = request.get.has_key("twocolumn") if l else False
thing.score_fmt = Score.safepoints
%>
${self.arrows(thing)}
<div class="reddit-entry entry ${thing.like_cls}"
@@ -84,7 +73,7 @@
%endif
<a class="reddit-comment-link"
${optionalstyle("color:gray")}
href="${permalink}">${com_label}</a>
href="${permalink}">${thing.comment_label}</a>
</small>
</div>
<div class="reddit-link-end" ${optionalstyle("clear:left; padding:3px;")}>

View File

@@ -26,7 +26,6 @@
from r2.models import FakeSubreddit
%>
<%
com_label = ungettext("comment", "comments", thing.num_comments)
url = add_sr(thing.permalink, force_hostname = True)
use_thumbs = thing.thumbnail and not request.GET.has_key("nothumbs")
%>
@@ -37,24 +36,31 @@
<pubDate>${thing._date.strftime('%a, %d %b %Y %H:%M:%S %z')}</pubDate>
<dc:date>${thing._date.isoformat()}</dc:date>
<description>
%if use_thumbs:
&lt;table&gt;
&lt;tr&gt;&lt;td&gt;
&lt;a href="${url}"&gt;&lt;img src="${thing.thumbnail}" alt="${thing.title}" title="${thing.title}" /&gt;&lt;/a&gt;
&lt;/td&gt;&lt;td&gt;
%endif
<% domain = get_domain(cname = c.cname, subreddit = False) %>
submitted by &lt;a href="http://${domain}/user/${thing.author.name}"&gt;${thing.author.name}&lt;/a&gt;
%if isinstance(c.site, FakeSubreddit):
to &lt;a href="http://${domain}${thing.subreddit.path}"&gt;
${thing.subreddit.name}&lt;/a&gt;
%endif
&lt;br/&gt;
&lt;a href="${thing.url}"&gt;[link]&lt;/a&gt;
&lt;a href="${url}"&gt;[${thing.num_comments} ${com_label}]&lt;/a&gt;
%if use_thumbs:
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
%endif
<%def name="description()" filter="h">
%if use_thumbs:
<table>
<tr><td>
<a href="${url}"><img src="${thing.thumbnail}" alt="${thing.title}" title="${thing.title}" /></a>
</td><td>
%endif
<%
domain = get_domain(cname = c.cname, subreddit = False)
%>
submitted by <a href="http://${domain}/user/${thing.author.name}">
${thing.author.name}
</a>
%if thing.different_sr:
to <a href="http://${domain}${thing.subreddit.path}">
${thing.subreddit.name}</a>
%endif
<br/>
<a href="${thing.url}">[link]</a>
<a href="${url}">[${thing.comment_label}]</a>
%if use_thumbs:
</td></tr></table>
%endif
</%def>
${description()}
</description>
%if use_thumbs:
<media:title>${thing.title}</media:title>

View File

@@ -19,7 +19,8 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="printable.html" import="state_button, thing_css_class" />
<%namespace file="printablebuttons.html" import="state_button" />
<%namespace file="printable.html" import="thing_css_class" />
<div class="raisedbox linkinfo">
<table class="details">

View File

@@ -22,7 +22,7 @@
<%!
from datetime import datetime
%>
<%namespace file="printable.html" import="ynbutton" />
<%namespace file="printablebuttons.html" import="ynbutton" />
<%namespace file="utils.html" import="plain_link" />
%if thing.a.promoted:

View File

@@ -20,7 +20,6 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<% from r2.lib.template_helpers import replace_render, add_sr %>
<%namespace file="utils.html" import="plain_link" />
<%
@@ -29,7 +28,7 @@
%>
<div id="siteTable${_id}" class="sitetable ${cls}">
%for a in thing.things:
${unsafe(replace_render(thing, a))}
${a}
%endfor
</div>

View File

@@ -20,8 +20,6 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%! from r2.lib.template_helpers import replace_render %>
<%namespace file="utils.html" import="optionalstyle"/>
<%namespace file="printable.html" import="thing_css_class"/>
<div ${optionalstyle("margin-left:5px;margin-top:7px;")}>
@@ -48,11 +46,7 @@
%endif
<div class="${cls} ${thing_css_class(a)}">
%if getattr(a, 'embed_voting_style', None) == 'votable':
${unsafe(replace_render(thing,a))}
%else:
${a.render()}
%endif
${a}
</div>
%if two_col and i == l - 1:
</div>

View File

@@ -25,7 +25,7 @@
<ul>
%for a in thing.things:
${a.render()}
${a}
%endfor:
</ul>

View File

@@ -19,7 +19,6 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
%for a in thing.things:
${a.render()}
${a}
%endfor:

View File

@@ -1,41 +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 CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
%if not isContent:
<span id="listing_error${parent!=UNDEFINED and '_%s'%str(parent) or ''}" class="error">${emptyStr}</span>
%endif
${ listing.render() }
%if prev or next:
<p class="menu"> ${"view more"}:
%if prev:
<a href="${prev}"
>&laquo; ${"prev"}</a>
%endif
%if next and prev:
|
%endif
%if next:
<a href="${next}"
>${"next"} &raquo;</a>
%endif
</p>
%endif

View File

@@ -30,13 +30,13 @@
<% op = "login-main" %>
<form method="post"
id="login_${op}" action="${add_sr('/post/login', nocname = True)}"
%if not c.frameless_cname or c.authorized_cname:
%if thing.auth_cname:
onsubmit="return post_user(this, 'login');"
%else:
onsubmit="return update_user(this);"
%endif
class="login-form-side">
%if c.cname:
%if thing.cname:
<input type="hidden" name="${UrlParser.cname_get}"
value="${random.random()}" />
%endif

View File

@@ -23,7 +23,7 @@
<div class="menuarea">
%for menu in thing.menus:
<div class="spacer">
${menu.render()}
${menu}
</div>
%endfor
</div>

View File

@@ -22,6 +22,7 @@
<%!
from r2.lib.filters import edit_comment_filter, safemarkdown
from r2.lib.pages.things import MessageButtons
%>
<%inherit file="comment_skeleton.html"/>
@@ -67,20 +68,7 @@ ${unsafe(safemarkdown(thing.body))}
</%def>
<%def name="buttons()">
%if hasattr(thing, "was_comment"):
<li>
${parent.bylink_button(_("context"), thing.permalink + "?context=3")}
</li>
${parent.delete_or_report_buttons(delete=False)}
%else:
${parent.delete_or_report_buttons(delete=False)}
${parent.buttons()}
%if c.user_is_loggedin:
<li>
${parent.simple_button(_("reply"), "reply")}
</li>
%endif
%endif
${MessageButtons(thing)}
</%def>
<%def name="entry()">

View File

@@ -51,14 +51,12 @@
<div class="spacer">
<%utils:round_field title="${_('message')}">
${UserText(None, have_form = False, creating = True).render()}
${UserText(None, have_form = False, creating = True)}
</%utils:round_field>
</div>
<div class="spacer">
%if thing.captcha:
${thing.captcha.render()}
%endif
${thing.captcha}
</div>
<button id="send" name="send" type="submit">${_("send")}</button>

View File

@@ -22,17 +22,18 @@
<%namespace file="utils.html" import="plain_link" />
<%def name="plain(selected = False)">
${plain_link(thing.selected_title() if selected else thing.title,
<%def name="plain()">
${plain_link(thing.selected_title() if thing.selected else thing.title,
thing.path, _sr_path = thing.sr_path, nocname = thing.nocname,
target = thing.target,
_class = thing.css_class, _id = thing._id)}
</%def>
<%def name="js(selected = False)">
${plain_link(thing.selected_title() if selected else thing.title,
<%def name="js()">
${plain_link(thing.selected_title() if thing.selected else thing.title,
thing.path, _sr_path = False, nocname = True,
_class = thing.css_class, _id = thing._id, onclick = thing.onclick)}
_class = thing.css_class, _id = thing._id,
onclick = thing.onclick)}
</%def>

View File

@@ -22,11 +22,11 @@
<%inherit file="navbutton.html" />
<%def name="plain(selected = False)">
${parent.plain(selected=selected)}
<%def name="plain()">
${parent.plain()}
</%def>
<%def name="js(selected = False)">
<%def name="js()">
</%def>

View File

@@ -23,6 +23,9 @@
<%namespace file="utils.html" import="plain_link, text_with_links, img_link, separator"/>
<%def name="dropdown()">
## caching comment:
## see caching comment for plain_link. In addition to the args,
## this function depends on c.site.name, c.render_style and c.cname.
<% css_class = str(thing.css_class) if thing.css_class else "" %>
%if thing:
%if thing.title and thing.selected:
@@ -69,16 +72,15 @@
##option.title isn't the title, option.render() is the entire link
if option == thing.selected:
class_name = "class='selected'"
content = option.render(selected=True)
option.selected = True
else:
class_name = ""
content = option.render()
%>
<li ${class_name}>
%if i > 0:
${separator(thing.separator)}
%endif
${content}
${option}
</li>
%endfor
</ul>
@@ -102,7 +104,7 @@
%if option == thing.selected:
<li class="selected ${cls}">${option.selected_title()}</li>
%else:
<li class="${cls}">${option.render()}</li>
<li class="${cls}">${option}</li>
%endif
%endfor
</ul>
@@ -116,10 +118,10 @@
${"id='%s'" % thing._id if thing._id else ""}>
%for i, option in enumerate(thing):
<%
selected = (option == thing.selected)
option.selected = (option == thing.selected)
%>
<li ${"class='selected'" if selected else ""}>
${option.render(selected = selected)}
<li ${"class='selected'" if option.selected else ""}>
${option}
</li>
%endfor
</ul>

View File

@@ -36,7 +36,7 @@
_class="submit content",
_id="newlink">
${thing.formtabs_menu.render()}
${thing.formtabs_menu}
<div class="formtabs-content">
<div class="spacer">
@@ -69,7 +69,7 @@ ${thing.formtabs_menu.render()}
<div class="spacer">
<%utils:round_field title="${_('text')}", description="${_('(optional)')}" id="text-field">
<input name="kind" value="self" type="hidden"/>
${UserText(None, have_form = False, creating = True).render()}
${UserText(None, have_form = False, creating = True)}
</%utils:round_field>
</div>
@@ -108,9 +108,7 @@ ${thing.formtabs_menu.render()}
</%utils:round_field>
</div>
%if thing.captcha:
${thing.captcha.render()}
%endif
${thing.captcha}
</div>
@@ -126,5 +124,5 @@ ${thing.formtabs_menu.render()}
if(form.length) {
var default_menu = form.find(".${thing.default_tab}:first");
select_form_tab(default_menu, "${thing.default_show}", "${thing.default_hide}");
}
}
</script>

View File

@@ -20,9 +20,9 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="printable.html" import="ynbutton"/>
<%namespace file="printablebuttons.html" import="ynbutton"/>
<%
from r2.lib.template_helpers import replace_render, static
from r2.lib.template_helpers import static
from r2.lib.promote import get_promoted
%>
@@ -43,8 +43,7 @@
<% pass %>
%elif lookup.has_key(name):
<% seen.add(name) %>
${unsafe(replace_render(thing, lookup[name],
display = (thing.visible_link == name)))}
${unsafe(lookup[name].render(display = (thing.visible_link == name)))}
%else:
<div class="thing id-${name} stub" style="display:none"></div>
%endif

View File

@@ -1,23 +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 CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
${thing.content and thing.content.render() or ''}

View File

@@ -1,23 +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 CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
${thing.content and thing.content.render() or ''}

View File

@@ -30,7 +30,7 @@
<h1>${thing.title}</h1>
%endif
${t.render() if t else ''}
${t}
%if thing.div:
</div>

View File

@@ -21,5 +21,5 @@
################################################################################
%for t in thing.stack:
${t.render()}
${t}
%endfor

View File

@@ -21,5 +21,5 @@
################################################################################
%for t in thing.stack:
${t.render() if t else ""}
${t}
%endfor

View File

@@ -21,5 +21,5 @@
################################################################################
%for t in thing.stack:
${t.render() if t else ''}
${t}
%endfor

View File

@@ -23,6 +23,7 @@
<%!
from r2.lib.template_helpers import add_sr
from r2.lib.strings import strings
from r2.lib.pages.things import BanButtons
%>
<%namespace file="utils.html" import="plain_link" />
@@ -61,9 +62,6 @@ thing id-${what._fullname}
else:
cls = thing.lookups[0].__class__.__name__.lower()
if cls == 'link' and c.user.pref_compress:
cls = 'compressed link'
if thing.show_spam:
rowclass = thing.rowstyle + " spam"
elif thing.show_reports:
@@ -75,7 +73,7 @@ thing id-${what._fullname}
if hasattr(thing, "hidden") and thing.hidden:
rowclass += " hidden"
%>
<div class="${self.thing_css_class(thing)} ${rowclass} ${cls}" $display>
<div class="${self.thing_css_class(thing)} ${rowclass} ${cls}" ${thing.display}>
<p class="parent">
${self.ParentDiv()}
</p>
@@ -98,43 +96,9 @@ thing id-${what._fullname}
</%def>
<%def name="buttons(ban=True)">
%if thing.can_ban and ban:
%if thing.show_spam:
<li>
${self.state_button("unban", _("unban"),
"return change_state(this, 'unban');", _("unbanned"))}
</li>
%else:
<li>
${self.state_button("ban", _("ban"),
"return change_state(this, 'ban');", _("banned"))}
</li>
%endif
%if thing.show_reports:
<li>
${self.state_button("ignore", _("ignore"), \
"change_state(this, 'ignore');", _("ignored"))}
</li>
%endif
%endif
${BanButtons(thing, show_ban = ban)}
</%def>
<%def name="delete_or_report_buttons(delete=True, report=True)">
%if c.user_is_loggedin:
%if (not thing.author or thing.author.name != c.user.name) and report:
<li>
${ynbutton(_("report"), _("reported"), "report", "hide_thing")}
</li>
%endif
%if (not thing.author or thing.author.name == c.user.name) and delete and not thing._deleted:
<li>
${ynbutton(_("delete"), _("deleted"), "del", "hide_thing")}
</li>
%endif
%endif
</%def>
<%def name="ParentDiv()">
</%def>
@@ -144,11 +108,9 @@ thing id-${what._fullname}
<%def name="entry()">
</%def>
<%def name="Child(display=True, render_hook=True)">
<%def name="Child(display=True)">
<div class="child" ${(not display and "style='display:none'" or "")}>
%if render_hook:
$child
%endif
${thing.childlisting}
</div>
</%def>
@@ -193,7 +155,7 @@ thing id-${what._fullname}
%>
<div class="arrow ${_class}"
%if c.user_is_loggedin:
onclick="$(this).vote('$votehash')"
onclick="$(this).vote('${thing.votehash}')"
%else:
onclick="showcover(true, 'vote_${fullname}')"
%endif
@@ -201,23 +163,16 @@ thing id-${what._fullname}
</div>
</%def>
<%def name="score(this, likes=None, tag='span', score_fmt = None)">
<%
score = this.score
base_score = score - 1 if likes else score if likes is None else score + 1
base_score = [base_score + x for x in range(-1, 2)]
if score_fmt is None:
score_fmt = thing.score_fmt
%>
<${tag} class="score dislikes">
${score_fmt(base_score[0])}
</${tag}>
<${tag} class="score unvoted">
${score_fmt(base_score[1])}
</${tag}>
<${tag} class="score likes">
${score_fmt(base_score[2])}
</${tag}>
<%def name="score(this, likes=None, scores = None, tag='span')">
<%
if scores is None:
scores = this.display_score
%>
%for cls, score in zip(["dislikes", "unvoted", "likes"], scores):
<${tag} class="score ${cls}">
${score}
</${tag}>
%endfor
</%def>
@@ -228,151 +183,3 @@ thing id-${what._fullname}
${self.arrow(thing, 0, thing.likes == False)}
</div>
</%def>
##
##
##
### originally in statebuttons
<%def name="state_button(name, title, onclick, executed, clicked=False, a_class = '', fmt=None, fmt_param = '', hidden_data = {})">
<%def name="_link()" buffered="True">
<a href="javascript:void()"
%if a_class:
class="${a_class}"
%endif
onclick="${onclick}">${title}</a>
</%def>
<%
link = _link()
if fmt:
link = fmt % {fmt_param: link}
## preserve spaces before and after < & > for space compression
link = link.replace(" <", "&#32;<").replace("> ", ">&#32;")
%>
%if clicked:
${executed}
%else:
<form action="/post/${name}" method="post"
class="state-button ${name}-button">
<input type="hidden" name="executed" value="${executed}" />
%for key, value in hidden_data.iteritems():
<input type="hidden" name="${key}" value="${value}" />
%endfor
<span>
${unsafe(link)}
</span>
</form>
%endif
</%def>
<%def name="ynbutton(title, executed, op, callback = 'null',
question = None,
format = '%(link)s',
format_arg = 'link',
hidden_data = {})">
<%
if question is None:
question = _("are you sure?")
link = ('<a href="#" onclick="return toggle(this)">'
+ title + '</a>')
link = format % {format_arg : link}
link = unsafe(link.replace(" <", "&#32;<").replace("> ", ">&#32;"))
%>
<form class="toggle ${op}-button" action="#" method="get">
<input type="hidden" name="executed" value="${executed}"/>
%for k, v in hidden_data.iteritems():
<input type="hidden" name="${k}" value="${v}"/>
%endfor
<span class="option active">
${link}
</span>
<span class="option error">
${question}
&#32;<a href="javascript:void(0)" class="yes"
onclick='change_state(this, "${op}", ${callback})'>
${_("yes")}
</a>&#32;/&#32;
<a href="javascript:void(0)" class="no"
onclick="return toggle(this)">${_("no")}</a>
</span>
</form>
</%def>
<%def name="simple_button(title, nameFunc)">
<a class="" href="javascript:void(0)"
onclick="return ${nameFunc}(this)">${title}</a>
</%def>
<%def name="toggle_button(class_name, title, alt_title,
callback, cancelback,
css_class = '', alt_css_class = '',
reverse = False,
login_required = False,
style = '',)">
<%
if reverse:
callback, cancelback = cancelback, callback
title, alt_title = alt_title, title
css_class, alt_css_class = alt_css_class, css_class
%>
<span class="${class_name} toggle" style="${style}">
<a class="option active ${css_class}" href="#" tabindex="100"
%if login_required and not c.user_is_loggedin:
onclick="return showcover('', '${callback}_' + $(this).thing_id());"
%else:
onclick="return toggle(this, ${callback}, ${cancelback})"
%endif
>
${title}
</a>
<a class="option ${alt_css_class}" href="#">${alt_title}</a>
</span>
</%def>
<%def name="toggleable_label(class_name,
title, alt_title,
callback, cancelback,
reverse = False)">
<%
if reverse:
callback, cancelback = cancelback, callback
title, alt_title = alt_title, title
%>
<span class="${class_name} toggle">
<span class="toggle option active">${title}</span>
<span class="toggle option">${alt_title}</span>
&#32;(
<a href="#"
onclick="return toggle_label(this, ${callback}, ${cancelback})"
>
${_("toggle")}
</a>
)
</span>
</%def>
<%def name="tags(**kw)">
%for k, v in kw.iteritems():
%if v is not None:
${k.strip('_')}="${v}" \
%endif
%endfor
</%def>
### originally in commentbutton
<%def name="comment_button(name, link_text, num, link,\
a_class='', title='', newwindow = False)">
<%
cls = "comments empty" if num == 0 else "comments"
if num > 0:
link_text = strings.number_label % dict(num=num, thing=link_text)
target = '_blank' if newwindow else '_parent'
%>
${plain_link(link_text, link,
_class="%s %s" % (a_class, cls), title=title, target=target)}
</%def>

View File

@@ -39,7 +39,7 @@ ${self.Child()}
</%def>
<%def name="Child()">
${hasattr(thing, "child") and thing.child.render() or ''}
${getattr(thing, "child", '')}
</%def>
<%def name="entry()">

View File

@@ -34,7 +34,7 @@
<%def name="Child()">
%if hasattr(thing, "child"):
<div id="child_${thing._fullname}" class="child">
${hasattr(thing, "child") and thing.child.render() or ''}
${thing.child}
</div>
%endif
</%def>

View File

@@ -25,7 +25,7 @@
from r2.lib.utils import timesince
%>
<%namespace file="utils.html" import="submit_form, plain_link"/>
<%namespace file="printable.html" import="toggle_button"/>
<%namespace file="printablebuttons.html" import="toggle_button"/>
<% user = thing.user %>
%if thing.user:
<div class="raisedbox">

View File

@@ -24,6 +24,7 @@
%>
<%inherit file="link.html"/>
<%namespace file="printablebuttons.html" import="ynbutton" />
<%namespace file="utils.html" import="plain_link" />
<%def name="tagline()">
@@ -36,7 +37,7 @@
_sr_path = False)}
</li>
<li>
${self.ynbutton(_("unpromote"), _("unpromoted"), "unpromote")}
${ynbutton(_("unpromote"), _("unpromoted"), "unpromote")}
</li>
%endif
</%def>

View File

@@ -23,7 +23,7 @@
from r2.lib.utils import to36
%>
<%namespace file="utils.html" import="plain_link"/>
<%namespace file="printable.html" import="ynbutton"/>
<%namespace file="printablebuttons.html" import="ynbutton"/>
<div>

View File

@@ -24,7 +24,7 @@
from r2.lib.media import thumbnail_url
%>
<%namespace file="utils.html" import="error_field, checkbox, plain_link, image_upload" />
<%namespace file="printable.html" import="ynbutton"/>
<%namespace file="printablebuttons.html" import="ynbutton"/>
<%namespace file="utils.html" import="error_field, checkbox, image_upload" />
%if thing.link:

View File

@@ -21,10 +21,13 @@
################################################################################
<%!
from r2.lib.template_helpers import add_sr, static, join_urls, class_dict, path_info
from r2.lib.template_helpers import add_sr, static, join_urls, class_dict, path_info, get_domain
from r2.lib.pages import SearchForm, ClickGadget
from r2.lib import tracking
from pylons import request
from r2.lib.strings import strings
%>
<%namespace file="login.html" import="login_panel, login_form"/>
<%namespace file="framebuster.html" import="framebuster"/>
<%inherit file="base.html"/>
@@ -37,8 +40,6 @@
</%def>
<%def name="stylesheet()">
<% from r2.lib.template_helpers import static, get_domain %>
%if c.lang_rtl:
<link rel="stylesheet" href="${static(g.stylesheet_rtl)}"
type="text/css" />
@@ -112,21 +113,52 @@
##<div class="fixedwidth"><!--IE6sux--></div>
##<div class="clearleft"><!--IE6sux--></div>
<div class="content">
${thing.content().render()}
${thing.content()}
</div>
%endif
%if thing.footer:
<%include file="redditfooter.html"/>
%endif
${thing.footer}
%if not c.user_is_loggedin:
%if thing.enable_login_cover:
<div class="login-popup cover-overlay" style="display: none">
<div class="cover" onclick="return hidecover(this)"></div>
<div class="popup">
<h1 class="cover-msg">${strings.cover_msg}</h1>
${login_panel(login_form)}
<div style="text-align:center; clear: both">
<a href="#" onclick="return hidecover(this)">
${_("close this window")}
</a>
</div>
</div>
</div>
%endif
<div class="lang-popup cover-overlay" style="display: none">
<div class="cover" onclick="return hidecover(this)"></div>
<div class="popup">
<%include file="prefoptions.html" />
<div style="text-align:center; clear:both;">
<a href="#" onclick="return hidecover(this)">
${_("close this window")}
</a>
</div>
</div>
</div>
%endif
%if g.tracker_url and thing.site_tracking:
<img alt="" src="${tracking.UserInfo.gen_url()}"/>
%endif
%endif
${framebuster()}
</body>
</%def>
<%def name="sidebar(content=None)">
${content.render() if content else ''}
${content}
%if c.user_is_admin:
<%include file="admin_rightbox.html"/>
@@ -135,7 +167,7 @@
%endif
##cheating... we should move ads into a template of its own
${ClickGadget(c.recent_clicks).render()}
${ClickGadget(c.recent_clicks)}
</%def>

View File

@@ -22,4 +22,4 @@
<%inherit file="base.htmllite"/>
${thing.content and thing.content().render() or ''}
${thing.content and thing.content() or ''}

View File

@@ -24,7 +24,7 @@
<%include file="redditheader.mobile"/>
${thing.content and thing.content().render() or ''}
${thing.content and thing.content() or ''}
<%def name="Title()">
%if thing.title:

View File

@@ -22,4 +22,4 @@
<%inherit file="base.xml"/>
${thing.content().render()}
${thing.content()}

View File

@@ -21,65 +21,29 @@
################################################################################
<%!
from r2.lib.template_helpers import get_domain, static, UrlParser
from r2.lib.strings import strings
from r2.lib import tracking
import random, datetime
import datetime
%>
<%namespace file="login.html" import="login_panel, login_form"/>
<%namespace file="utils.html" import="text_with_links, plain_link"/>
<%namespace file="utils.html" import="text_with_links"/>
<div class="footer-parent">
<div class="footer rounded">
%for toolbar in thing.footer_nav():
%for toolbar in thing.nav():
<div class="col">
${toolbar.render()}
${toolbar}
</div>
%endfor
<p class="bottommenu">
${text_with_links(_("Use of this site constitutes acceptance of our %(user_agreement)s and %(privacy_policy)s"), nocname=True, user_agreement= (_("User Agreement {Genitive}"), "http://reddit.com/help/useragreement"), privacy_policy = (_("Privacy Policy {Genitive}"), "http://reddit.com/help/privacypolicy"))}.
${text_with_links(
_("Use of this site constitutes acceptance of our "
"%(user_agreement)s and %(privacy_policy)s"), nocname=True,
user_agreement= (_("User Agreement {Genitive}"),
"http://reddit.com/help/useragreement"),
privacy_policy = (_("Privacy Policy {Genitive}"),
"http://reddit.com/help/privacypolicy"))}.
${_("(c) %(year)d Conde Nast Digital. All rights reserved.") % \
dict(year=datetime.datetime.now().timetuple()[0])}
</p>
%if g.tracker_url and thing.site_tracking:
<img alt="" src="${tracking.UserInfo.gen_url()}"/>
%endif
</div>
</div>
%if not c.user_is_loggedin:
%if thing.enable_login_cover:
${self.loginpopup(login_panel, login_form)}
%endif
${self.langpopup()}
%endif
<%def name="loginpopup(lp, lf)">
<div class="login-popup cover-overlay" style="display: none">
<div class="cover" onclick="return hidecover(this)"></div>
<div class="popup">
<h1 class="cover-msg">${strings.cover_msg}</h1>
${lp(lf)}
<div style="text-align:center; clear: both">
<a href="#" onclick="return hidecover(this)">
${_("close this window")}
</a>
</div>
</div>
</div>
</%def>
<%def name="langpopup()">
<div class="lang-popup cover-overlay" style="display: none">
<div class="cover" onclick="return hidecover(this)"></div>
<div class="popup">
<%include file="prefoptions.html" />
<div style="text-align:center; clear:both;">
<a href="#" onclick="return hidecover(this)">
${_("close this window")}
</a>
</div>
</div>
</div>
</%def>

View File

@@ -29,10 +29,7 @@
<%namespace file="utils.html" import="plain_link, text_with_js, img_link, separator, logout"/>
<div id="header">
%if thing.srtopbar:
${thing.srtopbar.render()}
%endif
${thing.srtopbar}
<div id="header-bottom-${'right' if c.lang_rtl else 'left'}">
<%
if c.site.header and c.allow_styles:
@@ -47,7 +44,7 @@
&nbsp;
%for toolbar in thing.toolbars:
${toolbar.render()}
${toolbar}
%endfor
</div>
@@ -83,7 +80,7 @@
${separator("|")}
%endif
${thing.corner_buttons().render()}
${thing.corner_buttons()}
%if c.user_is_loggedin:
${separator("|")}
${logout()}

View File

@@ -29,6 +29,6 @@
<div id="header">
<a href="/"><img id="header-img" src="${static('littlehead.png')}" alt="${c.site.name}" /></a><a href="/reddits" class="or">other reddits</a>
%for toolbar in thing.toolbars:
${toolbar.render()}
${toolbar}
%endfor
</div>

View File

@@ -25,7 +25,7 @@
<body onclick="close_menus()" class="min-body">
%if thing.content:
<div class="content">
${thing.content().render()}
${thing.content()}
</div>
%endif
</body>

View File

@@ -30,7 +30,7 @@
<h4 style="color:gray">${thing.header}</h4>
<div id="previoussearch">
${SearchForm(prev_search = thing.prev_search).render()}
${SearchForm(prev_search = thing.prev_search)}
</div>
%if thing.prev_search:

View File

@@ -37,7 +37,7 @@
</span>
</th>
<td>
<input class="real-name" value="${c.user.name}"
<input class="real-name" value="${thing.username}"
type="text" id="share_from_${thing.link_name}"
name="share_from" />
</td>
@@ -57,7 +57,7 @@
<td>
<input name="replyto" type="text" size="30"
id="replyto_${thing.link_name}"
value="${c.user.email if hasattr(c.user, 'email') else ''}"/>
value="${thing.email}"/>
</td>
<td class="reply-to-errors">
${error_field("BAD_EMAILS", "replyto")}

View File

@@ -26,8 +26,8 @@
%>
<%inherit file="printable.html"/>
<%namespace file="printablebuttons.html" import="toggle_button"/>
<%namespace file="utils.html" import="plain_link"/>
<%namespace file="printable.html" import="toggle_button"/>
<%def name="numcol()">
</%def>
@@ -47,10 +47,10 @@
</p>
%endif
<p class="tagline">
${tagline()}
${self.tagline()}
</p>
<ul class="flat-list buttons">
${buttons()}
${self.buttons()}
</ul>
</%def>
@@ -108,8 +108,3 @@
<%def name="child()">
</%def>
<%def name="buttons()">
${parent.delete_or_report_buttons(delete=False)}
${parent.buttons()}
</%def>

View File

@@ -21,41 +21,41 @@
################################################################################
<%!
from r2.models import FakeSubreddit
from r2.lib.utils import timesince
from r2.lib.strings import strings
%>
<%namespace file="printable.html" import="ynbutton, toggle_button, state_button"/>
<%namespace file="printablebuttons.html"
import="ynbutton, toggle_button, state_button"/>
<%namespace file="printable.html" import="thing_css_class" />
%if not isinstance(c.site, FakeSubreddit):
<div class="raisedbox subreddit-info ${thing_css_class(c.site)}">
<h3>${c.site.name}</h3>
%if c.user_is_loggedin:
%if not thing.is_fake:
<div class="raisedbox subreddit-info thing id-${thing.fullname}">
<h3>${thing.name}</h3>
%if thing.is_loggedin:
${toggle_button("subscribe-button",
_("subscribe"), _("unsubscribe"),
'subscribe("%s")' % c.site._fullname,
'unsubscribe("%s")' % c.site._fullname,
reverse = c.site.is_subscriber_defaults(c.user))}
'subscribe("%s")' % thing.fullname,
'unsubscribe("%s")' % thing.fullname,
reverse = thing.is_subscriber)}
%endif
<span class="label">
(${strings.person_label % dict(num = c.site._ups,
persons = ungettext('subscriber', 'subscribers', c.site._ups))})
(${strings.person_label % dict(num = thing.subscribers,
persons = ungettext('subscriber', 'subscribers',
thing.subscribers))})
</span>
<p>
${_("a community for %(time)s") % dict(time=timesince(c.site._date))}
${_("a community for %(time)s") % dict(time=timesince(thing.date))}
</p>
<div id="${c.site._fullname}" class="thing">
%if c.site.type in ("private", "restricted") and c.user_is_loggedin and \
c.site.is_contributor(c.user):
<div id="${thing.fullname}" class="thing">
%if thing.is_contributor:
${moderate_button("leave_contributor",
_("you are a contributor of this reddit. (%(leave)s)"),
_("stop being a contributor?"),
_("you are no longer a contributor"))}
%endif
%if c.user_is_loggedin and c.site.is_moderator(c.user):
%if thing.is_moderator:
${moderate_button("leave_moderator",
_("you are a moderator of this reddit. (%(leave)s)"),
_("stop being a moderator?"),
@@ -63,12 +63,12 @@
%endif
</div>
%if c.user_is_admin:
%if c.site._spam:
%if thing.is_admin:
%if thing.spam:
${state_button("unban", _("unban"),
"return change_state(this, 'unban');", _("unbanned"))}
%if hasattr(c.site, 'banner'):
<div>${strings.banned_by % c.site.banner}</div>
%if thing.banner:
<div>${strings.banned_by % thing.banner}</div>
%endif
%else:
${state_button("ban", _("ban"),
@@ -77,7 +77,7 @@
%endif
<div class="spacer">
%for n in thing.nav():
${n.render()}
${n}
%endfor
</div>
</div>

View File

@@ -25,7 +25,7 @@
%>
<%namespace file="utils.html" import="error_field, image_upload"/>
<%namespace file="printable.html" import="ynbutton, toggle_button"/>
<%namespace file="printablebuttons.html" import="ynbutton, toggle_button"/>
<div class="stylesheet-customize-container">
<form

View File

@@ -23,7 +23,7 @@
<%namespace file="utils.html" import="plain_link"/>
<div id="sr-header-area">
${thing.sr_dropdown.render()}
${thing.sr_bar.render()}
${thing.my_reddits_dropdown()}
${thing.recent_reddits()}
${plain_link(unsafe(_("more") + " &raquo;"), "/reddits/", id="sr-more-link")}
</div>

View File

@@ -1,9 +1,9 @@
${thing.tabmenu.render()}
${thing.tabmenu}
<div class="tabpane-content">
%for i, (name, title, pane) in enumerate(thing.tabs):
<div id="tabbedpane-${name}" class="tabbedpane"
${"style='display:none'" if i > 0 else ""}>
${pane.render()}
${pane}
</div>
%endfor
##select the front tab and init the other tabs

View File

@@ -161,7 +161,7 @@ $(function() {
%endif
<table style="border-spacing: 5px">
%endif
${s.render()}
${s}
%endfor
</table>
<form>

View File

@@ -43,7 +43,7 @@
<table>
%if thing.users:
%for item in thing.users:
${item.render()}
${item}
%endfor
%else:
<tr><td></td></tr>

View File

@@ -20,7 +20,7 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="printable.html" import="ynbutton" />
<%namespace file="printablebuttons.html" import="ynbutton" />
<%namespace file="utils.html" import="plain_link"/>
<tr>

View File

@@ -24,7 +24,7 @@
from r2.lib.utils import randstr
%>
<%namespace file="printable.html" import="toggle_button" />
<%namespace file="printablebuttons.html" import="toggle_button" />
<%namespace file="utils.html" import="error_field"/>
<%def name="action_button(name, btn_type, onclick, display)">
@@ -38,14 +38,13 @@
<form action="#" class="${thing.css_class}"
onsubmit="return post_form(this, '${thing.post_form}')"
${"style='display:none'" if not thing.display else ""}
id="form-${(thing.item._fullname if thing.item else "") + randstr(3)}">
id="form-${thing.fullname + randstr(3)}">
%else:
<div class="${thing.css_class}">
%endif
##this is set for both editting selftext and creating new comments
<input type="hidden" name="thing_id"
value="${thing.item and thing.item._fullname or ''}"/>
<input type="hidden" name="thing_id" value="${thing.fullname}"/>
%if not thing.creating:
<div class="usertext-body">

View File

@@ -114,6 +114,13 @@ ${first_defined(kw[1:])}
</%def>
<%def name="plain_link(link_text, path, _sr_path = True, nocname=False, fmt='', target='', **kw)">
## caching comment:
## in addition to the args, this function also makes use of c.cname,
## and, via add_sr, both c.site.name and c.render_style.
##
## This function is called by (among other places) NavMenu as the
## primary rendering view. Any changes to the c-usage of this function
## will have to be propagated up.
<%
if (not target or target == '_parent') and c.cname:
target = '_top'
@@ -122,7 +129,9 @@ ${first_defined(kw[1:])}
if target:
kw['target'] = target
link = _a_buffered(link_text, href=add_sr(path, sr_path=_sr_path, nocname=nocname), **kw)
link = _a_buffered(link_text,
href=add_sr(path, sr_path=_sr_path, nocname=nocname),
**kw)
%>
${unsafe((fmt % link) if fmt else link)}