Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| # -*- coding: utf-8 -*- | |
| # 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-2008 | |
| # CondeNet, Inc. All Rights Reserved. | |
| ################################################################################ | |
| from r2.lib.wrapped import Wrapped, NoTemplateFound | |
| from r2.models import * | |
| from r2.config import cache | |
| from r2.lib.jsonresponse import json_respond | |
| from r2.lib.jsontemplates import is_api | |
| from pylons.i18n import _ | |
| from pylons import c, request, g | |
| from pylons.controllers.util import abort | |
| from r2.lib.captcha import get_iden | |
| from r2.lib.filters import spaceCompress, _force_unicode, _force_utf8 | |
| from r2.lib.db.queries import db_sort | |
| from r2.lib.menus import NavButton, NamedButton, NavMenu, JsButton, ExpandableButton, AbsButton | |
| from r2.lib.menus import SubredditButton, SubredditMenu, menu | |
| from r2.lib.strings import plurals, rand_strings, strings | |
| from r2.lib.utils import title_to_url, query_string, UrlParser | |
| from r2.lib.template_helpers import add_sr, get_domain | |
| from r2.lib.promote import promote_builder_wrapper | |
| from r2.lib.wikipagecached import WikiPageCached | |
| import sys | |
| datefmt = _force_utf8(_('%d %b %Y')) | |
| def get_captcha(): | |
| if not c.user_is_loggedin or c.user.needs_captcha(): | |
| return get_iden() | |
| class Reddit(Wrapped): | |
| '''Base class for rendering a page on reddit. Handles toolbar creation, | |
| content of the footers, and content of the corner buttons. | |
| Constructor arguments: | |
| space_compress -- run r2.lib.filters.spaceCompress on render | |
| loginbox -- enable/disable rendering of the small login box in the right margin | |
| (only if no user is logged in; login box will be disabled for a logged in user) | |
| show_sidebar -- enable/disable content in the right margin | |
| infotext -- text to display in a <p class="infotext"> above the content | |
| nav_menus -- list of Menu objects to be shown in the area below the header | |
| content -- renderable object to fill the main content well in the page. | |
| settings determined at class-declaration time | |
| create_reddit_box -- enable/disable display of the "Creat a reddit" box | |
| submit_box -- enable/disable display of the "Submit" box | |
| searcbox -- enable/disable display of the "search" box in the header | |
| extension_handling -- enable/disable rendering using non-html templates | |
| (e.g. js, xml for rss, etc.) | |
| ''' | |
| create_reddit_box = False | |
| submit_box = False | |
| searchbox = True | |
| extension_handling = True | |
| def __init__(self, space_compress = True, nav_menus = None, loginbox = True, | |
| infotext = '', content = None, title = '', robots = None, sidewiki = True, | |
| show_sidebar = True, body_class = None, top_filter = None, header_sub_nav = [], **context): | |
| Wrapped.__init__(self, **context) | |
| self.title = title | |
| self.robots = robots | |
| self.infotext = infotext | |
| self.loginbox = True | |
| self.show_sidebar = show_sidebar | |
| self.space_compress = space_compress | |
| self.body_class = body_class | |
| self.top_filter = top_filter | |
| self.header_sub_nav = header_sub_nav | |
| self.sidewiki = sidewiki | |
| # by default, assume the canonical URLs are the ones without query params | |
| if request.GET: | |
| self.canonical_link = request.path | |
| #put the sort menus at the top | |
| self.nav_menu = MenuArea(menus = nav_menus) if nav_menus else None | |
| #add the infobar | |
| self.infobar = None | |
| if c.firsttime and c.site.firsttext and not infotext: | |
| infotext = c.site.firsttext | |
| if not infotext and hasattr(c.site, 'infotext'): | |
| infotext = c.site.infotext | |
| if infotext: | |
| self.infobar = InfoBar(message = infotext) | |
| self.srtopbar = None | |
| if not c.cname: | |
| self.srtopbar = SubredditTopBar() | |
| self._content = content | |
| self.toolbars = self.build_toolbars() | |
| def rightbox(self): | |
| """generates content in <div class="rightbox">""" | |
| ps = PaneStack(css_class='spacer') | |
| if self.searchbox: | |
| ps.append(GoogleSearchForm()) | |
| if not c.user_is_loggedin and self.loginbox: | |
| ps.append(LoginFormWide()) | |
| else: | |
| ps.append(ProfileBar(c.user, self.corner_buttons())) | |
| if (c.user_is_loggedin and | |
| c.user.wiki_account is None and | |
| c.user.email is not None and | |
| c.user.email_validated and | |
| self.sidewiki): | |
| ps.append(WikiCreateSide()) | |
| filters_ps = PaneStack(div=True) | |
| for toolbar in self.toolbars: | |
| filters_ps.append(toolbar) | |
| if self.nav_menu: | |
| filters_ps.append(self.nav_menu) | |
| if not filters_ps.empty: | |
| ps.append(SideBox(filters_ps)) | |
| #don't show the subreddit info bar on cnames | |
| if c.user_is_admin and not isinstance(c.site, FakeSubreddit) and not c.cname: | |
| ps.append(SubredditInfoBar()) | |
| if self.extension_handling: | |
| ps.append(FeedLinkBar(getattr(self, 'canonical_link', request.path))) | |
| ps.append(SideBoxPlaceholder('side-meetups', _('Nearest Meetups'), '/meetups', sr_path=False)) | |
| ps.append(VirtualStudyRoom()) | |
| ps.append(SideBoxPlaceholder('side-comments', _('Recent Comments'), '/comments')) | |
| ps.append(SideBoxPlaceholder('side-posts', _('Recent Posts'), '/recentposts')) | |
| if c.site.name == 'discussion': | |
| ps.append(SideBoxPlaceholder('side-open', _('Recent Open Threads'), '/tag/open_thread')) | |
| ps.append(SideBoxPlaceholder('side-diary', _('Recent Rationality Diaries'), '/tag/group_rationality_diary')) | |
| else: | |
| ps.append(SideBoxPlaceholder('side-quote', _('Recent Rationality Quotes'), '/tag/quotes')) | |
| if g.recent_edits_feed: | |
| ps.append(RecentWikiEditsBox(g.recent_edits_feed)) | |
| ps.append(FeedBox(g.feedbox_urls)) | |
| ps.append(SideBoxPlaceholder('side-monthly-contributors', _('Top Contributors, 30 Days'))) | |
| ps.append(SideBoxPlaceholder('karma-awards', _('Recent Karma Awards'), '/karma', sr_path=False)) | |
| if g.site_meter_codename: | |
| ps.append(SiteMeter(g.site_meter_codename)) | |
| return ps | |
| def render(self, *a, **kw): | |
| """Overrides default Wrapped.render with two additions | |
| * support for rendering API requests with proper wrapping | |
| * support for space compression of the result | |
| In adition, unlike Wrapped.render, the result is in the form of a pylons | |
| Response object with it's content set. | |
| """ | |
| try: | |
| res = Wrapped.render(self, *a, **kw) | |
| if is_api(): | |
| res = json_respond(res) | |
| elif self.space_compress: | |
| res = spaceCompress(res) | |
| c.response.content = res | |
| except NoTemplateFound, e: | |
| # re-raise the error -- development environment | |
| if g.debug: | |
| s = sys.exc_info() | |
| raise s[1], None, s[2] | |
| # die gracefully -- production environment | |
| else: | |
| abort(404, "not found") | |
| return c.response | |
| def corner_buttons(self): | |
| """set up for buttons in upper right corner of main page.""" | |
| buttons = [] | |
| if c.user_is_loggedin: | |
| if c.user.name in g.admins: | |
| if c.user_is_admin: | |
| buttons += [NamedButton("adminoff", False, | |
| nocname=not c.authorized_cname, | |
| target = "_self")] | |
| else: | |
| buttons += [NamedButton("adminon", False, | |
| nocname=not c.authorized_cname, | |
| target = "_self")] | |
| buttons += [NamedButton('submit', sr_path = not c.default_sr, | |
| nocname=not c.authorized_cname)] | |
| buttons += [NamedButton('submitlink', dest = 'submit', | |
| dest_params = { 'link': True }, | |
| sr_path = not c.default_sr, | |
| nocname=not c.authorized_cname)] | |
| if c.user.safe_karma >= g.discussion_karma_to_post: | |
| buttons += [NamedButton('meetups/new', False, | |
| nocname=not c.authorized_cname)] | |
| buttons += [NamedButton("prefs", False, | |
| css_class = "pref-lang")] | |
| buttons += [NamedButton("logout", False, | |
| nocname=not c.authorized_cname, | |
| target = "_self")] | |
| return NavMenu(buttons, base_path = "/", type = "buttons") | |
| def footer_nav(self): | |
| """navigation buttons in the footer.""" | |
| buttons = [NamedButton("help", False, nocname=True), | |
| NamedButton("blog", False, nocname=True), | |
| NamedButton("stats", False, nocname=True), | |
| NamedButton("feedback", False), | |
| NamedButton("bookmarklets", False), | |
| NamedButton("socialite", False), | |
| NamedButton("buttons", True), | |
| NamedButton("widget", True), | |
| NamedButton("code", False, nocname=True), | |
| NamedButton("mobile", False, nocname=True), | |
| NamedButton("store", False, nocname=True), | |
| NamedButton("ad_inq", False, nocname=True), | |
| ] | |
| return NavMenu(buttons, base_path = "/", type = "flatlist") | |
| def header_nav(self): | |
| """Navigation menu for the header""" | |
| menu_stack = PaneStack() | |
| # Ensure the default button is the first tab | |
| #default_button_name = c.site.default_listing | |
| main_buttons = [ | |
| ExpandableButton('main', dest = '/promoted', sr_path = False, sub_menus = | |
| [ NamedButton('posts', dest = '/promoted', sr_path = False), | |
| NamedButton('comments', dest = '/comments', sr_path = False)]), | |
| ExpandableButton('discussion', dest = "/r/discussion/new", sub_reddit = "/r/discussion/", sub_menus = | |
| [ NamedButton('posts', dest = "/r/discussion/new", sr_path = False), | |
| NamedButton('comments', dest = "/r/discussion/comments", sr_path = False)]) | |
| ] | |
| menu_stack.append(NavMenu(main_buttons, title = _('Filter by'), _id='nav', type='navlist')) | |
| if self.header_sub_nav: | |
| menu_stack.append(NavMenu(self.header_sub_nav, title = _('Filter by'), _id='filternav', type='navlist')) | |
| return menu_stack | |
| def right_menu(self): | |
| """docstring for right_menu""" | |
| buttons = [ | |
| AbsButton('wiki', 'http://'+g.wiki_host), | |
| NamedButton('sequences', sr_path=False), | |
| NamedButton('about', sr_path=False) | |
| ] | |
| return NavMenu(buttons, title = _('Filter by'), _id='rightnav', type='navlist') | |
| def build_toolbars(self): | |
| """Additional toolbars/menus""" | |
| return [] | |
| def __repr__(self): | |
| return "<Reddit>" | |
| @staticmethod | |
| def content_stack(*a): | |
| """Helper method for reordering the content stack.""" | |
| return PaneStack(filter(None, a)) | |
| def content(self): | |
| """returns a Wrapped (or renderable) item for the main content div.""" | |
| return self.content_stack(self.infobar, self._content) | |
| class LoginFormWide(Wrapped): | |
| """generates a login form suitable for the 300px rightbox.""" | |
| pass | |
| class WikiCreateSide(Wrapped): | |
| """generates a sidebox for creating a wiki account.""" | |
| pass | |
| class SideBoxPlaceholder(Wrapped): | |
| """A minimal side box with a heading and an anchor. | |
| If javascript is off the anchor may be followed and if it is on | |
| then javascript will replace the content of the div with the HTML | |
| result of an ajax request. | |
| """ | |
| def __init__(self, node_id, link_text, link_path=None, sr_path=True): | |
| Wrapped.__init__(self, node_id=node_id, link_text=link_text, link_path=link_path, sr_path=sr_path) | |
| class SpaceCompressedWrapped(Wrapped): | |
| """Overrides default Wrapped.render to do space compression as well.""" | |
| def render(self, *a, **kw): | |
| res = Wrapped.render(self, *a, **kw) | |
| res = spaceCompress(res) | |
| return res | |
| class RecentItems(SpaceCompressedWrapped): | |
| def __init__(self, *args, **kwargs): | |
| self.things = self.init_builder() | |
| Wrapped.__init__(self, *args, **kwargs) | |
| def query(self): | |
| raise NotImplementedError | |
| def init_builder(self): | |
| return QueryBuilder(self.query(), wrap=self.wrap_thing) | |
| @staticmethod | |
| def wrap_thing(thing): | |
| w = Wrapped(thing) | |
| if isinstance(thing, Link): | |
| w.render_class = InlineArticle | |
| elif isinstance(thing, Comment): | |
| w.render_class = InlineComment | |
| return w | |
| class RecentComments(RecentItems): | |
| def query(self): | |
| return c.current_or_default_sr.get_comments('new', 'all') | |
| def init_builder(self): | |
| return UnbannedCommentBuilder( | |
| self.query(), | |
| num = 5, | |
| wrap = RecentItems.wrap_thing, | |
| skip = True, | |
| sr_ids = [c.current_or_default_sr._id] | |
| ) | |
| class RecentTagged(RecentItems): | |
| """Finds the most recent post associated with a given tag and shows the most | |
| most recent top level comment in that thread""" | |
| def __init__(self, *args, **kwargs): | |
| self.tag = kwargs['tagtype'] | |
| self.title = kwargs['title'] | |
| self.things = self.init_builder() | |
| Wrapped.__init__(self, *args, **kwargs) | |
| def query(self): | |
| t = LinkTag._query(LinkTag.c._thing2_id == Tag._by_name(self.tag)._id, | |
| LinkTag.c._name == 'tag', | |
| LinkTag.c._t1_deleted == False, | |
| sort = desc('_date'), | |
| limit = 1, | |
| eager_load = True, | |
| thing_data = not g.use_query_cache | |
| ) | |
| temp = list(t)[0]._thing1 | |
| relevantpost = temp._id | |
| self.url = temp.url | |
| q = Comment._query(Comment.c.link_id == relevantpost, | |
| Comment.c._deleted == False, | |
| Comment.c._spam == False, | |
| sort = desc('_date'), | |
| data = True) | |
| return q | |
| def init_builder(self): | |
| return ToplevelCommentBuilder( | |
| self.query(), | |
| num = 1, | |
| wrap = RecentItems.wrap_thing, | |
| skip = True, | |
| sr_ids = [c.current_or_default_sr._id] | |
| ) | |
| class RecentArticles(RecentItems): | |
| def query(self): | |
| q = c.current_or_default_sr.get_links('new', 'all') | |
| q._limit = 10 | |
| return q | |
| class RecentArticlesPage(Wrapped): | |
| """Compact recent article listing page""" | |
| def __init__(self, content, *a, **kw): | |
| Wrapped.__init__(self, content=content, *a, **kw) | |
| class KarmaPage(Wrapped): | |
| """Compact recent article listing page""" | |
| def __init__(self, content, *a, **kw): | |
| Wrapped.__init__(self, content=content, *a, **kw) | |
| class RecentPromotedArticles(RecentItems): | |
| def query(self): | |
| sr = DefaultSR() | |
| q = sr.get_links('blessed', 'all') | |
| q._limit = 4 | |
| return q | |
| class TopContributors(SpaceCompressedWrapped): | |
| def __init__(self, *args, **kwargs): | |
| from r2.lib.user_stats import top_users | |
| uids = top_users() | |
| users = Account._byID(uids, data=True, return_dict=False) | |
| # Filter out accounts banned from the default subreddit | |
| sr = Subreddit._by_name(g.default_sr) | |
| self.things = filter(lambda user: not sr.is_banned(user), users) | |
| Wrapped.__init__(self, *args, **kwargs) | |
| class TopMonthlyContributors(SpaceCompressedWrapped): | |
| def __init__(self, *args, **kwargs): | |
| from r2.lib.user_stats import cached_monthly_top_users | |
| uids_karma = cached_monthly_top_users() | |
| uids = map(lambda x: x[0], uids_karma) | |
| users = Account._byID(uids, data=True, return_dict=False) | |
| # Add the monthly karma to the account objects | |
| karma_lookup = dict(uids_karma) | |
| for u in users: | |
| pair = karma_lookup[u._id] | |
| u.monthly_karma = pair[0] - pair[1] | |
| # Filter out accounts banned from the default subreddit | |
| sr = Subreddit._by_name(g.default_sr) | |
| self.things = filter(lambda user: not sr.is_banned(user), users) | |
| Wrapped.__init__(self, *args, **kwargs) | |
| class TagCloud(SpaceCompressedWrapped): | |
| numbers = ('one','two','three','four','five','six','seven','eight','nine','ten') | |
| def nav(self): | |
| cloud = Tag.tag_cloud_for_subreddits([c.current_or_default_sr._id]) | |
| buttons = [] | |
| for tag, weight in cloud: | |
| buttons.append(NavButton(tag.name, tag.name, css_class=self.numbers[weight - 1])) | |
| return NavMenu(buttons, type="flatlist", separator=' ', base_path='/tag/') | |
| class SubredditInfoBar(Wrapped): | |
| """When not on Default, renders a sidebox which gives info about | |
| the current reddit, including links to the moderator and | |
| contributor pages, as well as links to the banning page if the | |
| current user is a moderator.""" | |
| def nav(self): | |
| is_moderator = c.user_is_loggedin and \ | |
| c.site.is_moderator(c.user) or c.user_is_admin | |
| buttons = [NavButton(plurals.moderators, 'moderators')] | |
| if c.site.type != 'public': | |
| buttons.append(NavButton(plurals.contributors, 'contributors')) | |
| if is_moderator: | |
| buttons.append(NamedButton('edit')) | |
| buttons.extend([NavButton(menu.banusers, 'banned'), | |
| NamedButton('spam')]) | |
| return [NavMenu(buttons, type = "flatlist", base_path = "/about/")] | |
| class SideBox(Wrapped): | |
| """Generic sidebox""" | |
| def __init__(self, content, _id = None, css_class = ''): | |
| Wrapped.__init__(self, content=content, _id = _id, css_class = css_class) | |
| class PrefsPage(Reddit): | |
| """container for pages accessible via /prefs. No extension handling.""" | |
| extension_handling = False | |
| def __init__(self, show_sidebar = True, *a, **kw): | |
| Reddit.__init__(self, show_sidebar = show_sidebar, | |
| title = "%s - %s" % (_("Preferences"), c.site.title), | |
| *a, **kw) | |
| def header_nav(self): | |
| buttons = [NavButton(menu.options, ''), | |
| NamedButton('friends'), | |
| NamedButton('update'), | |
| NamedButton('delete')] | |
| if c.user.wiki_account is None: | |
| buttons.append(NamedButton('wikiaccount')) | |
| elif c.user.wiki_account == '__error__': | |
| pass | |
| else: | |
| user_page_url = 'http://{0}/wiki/User:{1}'.format(g.wiki_host, c.user.wiki_account) | |
| buttons.append(NamedButton('wikiaccount', dest=user_page_url, style='external')) | |
| return NavMenu(buttons, base_path = "/prefs", _id='nav', type='navlist') | |
| class PrefOptions(Wrapped): | |
| """Preference form for updating language and display options""" | |
| def __init__(self, done = False): | |
| Wrapped.__init__(self, done = done) | |
| class PrefUpdate(Wrapped): | |
| """Preference form for updating email address and passwords""" | |
| pass | |
| class PrefDelete(Wrapped): | |
| """preference form for deleting a user's own account.""" | |
| pass | |
| class PrefWiki(Wrapped): | |
| """Preference form for creating a Wiki account.""" | |
| pass | |
| class MessagePage(Reddit): | |
| """Defines the content for /message/*""" | |
| def __init__(self, *a, **kw): | |
| if not kw.has_key('show_sidebar'): | |
| kw['show_sidebar'] = True | |
| Reddit.__init__(self, *a, **kw) | |
| self.replybox = CommentReplyBox() | |
| def content(self): | |
| return self.content_stack(self.replybox, self.infobar, self._content) | |
| def header_nav(self): | |
| buttons = [NamedButton('compose'), | |
| NamedButton('inbox'), | |
| NamedButton('sent')] | |
| return NavMenu(buttons, base_path = "/message", _id='nav', type='navlist') | |
| class MessageCompose(Wrapped): | |
| """Compose message form.""" | |
| def __init__(self,to='', subject='', message='', success='', | |
| captcha = None): | |
| Wrapped.__init__(self, to = to, subject = subject, | |
| message = message, success = success, | |
| captcha = captcha) | |
| class KarmaAwardPage(Reddit): | |
| """Defines the content for /message/*""" | |
| def __init__(self, *a, **kw): | |
| if not kw.has_key('show_sidebar'): | |
| kw['show_sidebar'] = True | |
| Reddit.__init__(self, *a, **kw) | |
| self.replybox = CommentReplyBox() | |
| def content(self): | |
| return self.content_stack(self.replybox, self.infobar, self._content) | |
| class KarmaAward(Wrapped): | |
| """Compose message form.""" | |
| def __init__(self,to='', amount='', reason='', success='', | |
| captcha = None): | |
| Wrapped.__init__(self, to = to, amount = amount, | |
| reason = reason, success = success, | |
| captcha = captcha) | |
| class BoringPage(Reddit): | |
| """parent class For rendering all sorts of uninteresting, | |
| sortless, navless form-centric pages. The top navmenu is | |
| populated only with the text provided with pagename and the page | |
| title is 'reddit.com: pagename'""" | |
| extension_handling= False | |
| def __init__(self, pagename, **context): | |
| self.pagename = pagename | |
| Reddit.__init__(self, title = "%s - %s" % (_force_unicode(pagename), c.site.title), | |
| **context) | |
| class FormPage(BoringPage): | |
| """intended for rendering forms with no rightbox needed or wanted""" | |
| def __init__(self, pagename, show_sidebar = False, *a, **kw): | |
| BoringPage.__init__(self, pagename, show_sidebar = show_sidebar, | |
| *a, **kw) | |
| class LoginPage(BoringPage): | |
| """a boring page which provides the Login/register form""" | |
| def __init__(self, **context): | |
| context['loginbox'] = False | |
| self.dest = context.get('dest', '') | |
| context['show_sidebar'] = False | |
| BoringPage.__init__(self, _("Login or register"), **context) | |
| def content(self): | |
| kw = {} | |
| for x in ('user_login', 'user_reg'): | |
| kw[x] = getattr(self, x) if hasattr(self, x) else '' | |
| return Login(dest = self.dest, **kw) | |
| class Login(Wrapped): | |
| """The two-unit login and register form.""" | |
| def __init__(self, user_reg = '', user_login = '', dest=''): | |
| Wrapped.__init__(self, user_reg = user_reg, user_login = user_login, | |
| dest = dest) | |
| class VoteMultiplierEditPage(BoringPage): | |
| searchbox = False | |
| navlist = False | |
| def __init__(self, title, user, *a, **kw): | |
| self.user = user | |
| BoringPage.__init__(self, title) | |
| def content(self): | |
| return VoteMultiplierEdit(self.user) | |
| class VoteMultiplierEdit(Wrapped): | |
| def __init__(self, user, success = False): | |
| Wrapped.__init__(self, success = success, | |
| username = user.name, | |
| vote_multiplier = user.vote_multiplier) | |
| class VerifyEmail(Wrapped): | |
| def __init__(self, success=False): | |
| Wrapped.__init__(self, success = success) | |
| class SearchPage(BoringPage): | |
| """Search results page""" | |
| searchbox = False | |
| def __init__(self, pagename, prev_search, elapsed_time, num_results, *a, **kw): | |
| self.searchbar = SearchBar(prev_search = prev_search, | |
| elapsed_time = elapsed_time, | |
| num_results = num_results) | |
| BoringPage.__init__(self, pagename, robots='noindex', *a, **kw) | |
| def content(self): | |
| return self.content_stack(self.searchbar, self.infobar, self._content) | |
| class LinkInfoPage(Reddit): | |
| """Renders the varied /info pages for a link. The Link object is | |
| passed via the link argument and the content passed to this class | |
| will be rendered after a one-element listing consisting of that | |
| link object. | |
| In addition, the rendering is reordered so that any nav_menus | |
| passed to this class will also be rendered underneath the rendered | |
| Link. | |
| """ | |
| create_reddit_box = False | |
| robots = None | |
| @staticmethod | |
| def comment_permalink_wrapper(comment, link): | |
| wrapped = Wrapped(link, link_title=comment.make_permalink_title(link), for_comment_permalink=True) | |
| wrapped.render_class = CommentPermalink | |
| return wrapped | |
| def __init__(self, link = None, comment = None, | |
| link_title = '', is_canonical = False, *a, **kw): | |
| link.render_full = True | |
| # TODO: temp hack until we find place for builder_wrapper | |
| from r2.controllers.listingcontroller import ListingController | |
| if comment: | |
| link_wrapper = lambda link: self.comment_permalink_wrapper(comment, link) | |
| else: | |
| link_wrapper = ListingController.builder_wrapper | |
| link_builder = IDBuilder(link._fullname, | |
| wrap = link_wrapper) | |
| # link_listing will be the one-element listing at the top | |
| self.link_listing = LinkListing(link_builder, nextprev=False).listing() | |
| # link is a wrapped Link object | |
| self.link = self.link_listing.things[0] | |
| link_title = ((self.link.title) if hasattr(self.link, 'title') else '') | |
| if comment: | |
| title = comment.make_permalink_title(link) | |
| # Comment permalinks should not be indexed, there's too many of them | |
| self.robots = 'noindex' | |
| if is_canonical == False: | |
| self.canonical_link = comment.make_permalink(link) | |
| else: | |
| params = {'title':_force_unicode(link_title), 'site' : c.site.title} | |
| title = strings.link_info_title % params | |
| if not (c.default_sr and is_canonical): | |
| # Not on the main page, so include a pointer to the canonical URL for this link | |
| self.canonical_link = link.canonical_url | |
| Reddit.__init__(self, title = title, body_class = 'post', robots = self.robots, *a, **kw) | |
| def content(self): | |
| return self.content_stack(self.infobar, self.link_listing, self._content) | |
| def build_toolbars(self): | |
| return [] | |
| class LinkInfoBar(Wrapped): | |
| """Right box for providing info about a link.""" | |
| def __init__(self, a = None): | |
| if a: | |
| a = Wrapped(a) | |
| Wrapped.__init__(self, a = a, datefmt = datefmt) | |
| class EditReddit(Reddit): | |
| """Container for the about page for a reddit""" | |
| extension_handling= False | |
| def __init__(self, *a, **kw): | |
| is_moderator = c.user_is_loggedin and \ | |
| c.site.is_moderator(c.user) or c.user_is_admin | |
| title = _('Manage your category') if is_moderator else \ | |
| _('About %(site)s') % dict(site=c.site.name) | |
| Reddit.__init__(self, title = title, *a, **kw) | |
| class SubredditsPage(Reddit): | |
| """container for rendering a list of reddits.""" | |
| submit_box = False | |
| def __init__(self, prev_search = '', num_results = 0, elapsed_time = 0, | |
| title = '', loginbox = True, infotext = None, *a, **kw): | |
| Reddit.__init__(self, title = title, loginbox = loginbox, infotext = infotext, | |
| *a, **kw) | |
| self.sr_infobar = InfoBar(message = strings.sr_subscribe) | |
| def header_nav(self): | |
| buttons = [NavButton(menu.popular, ""), | |
| NamedButton("new")] | |
| if c.user_is_admin: | |
| buttons.append(NamedButton("banned")) | |
| return NavMenu(buttons, base_path = '/categories') | |
| def content(self): | |
| return self.content_stack(self.sr_infobar, self._content) | |
| def rightbox(self): | |
| ps = Reddit.rightbox(self) | |
| position = 1 if not c.user_is_loggedin else 0 | |
| ps.insert(position, SubscriptionBox()) | |
| return ps | |
| class MySubredditsPage(SubredditsPage): | |
| """Same functionality as SubredditsPage, without the search box.""" | |
| def content(self): | |
| return self.content_stack(self.infobar, self._content) | |
| def votes_visible(user): | |
| """Determines whether to show/hide a user's votes. They are visible: | |
| * if the current user is the user in question | |
| * if the user has a preference showing votes | |
| * if the current user is an administrator | |
| """ | |
| return ((c.user_is_loggedin and c.user.name == user.name) or | |
| user.pref_public_votes or | |
| c.user_is_admin) | |
| class ProfilePage(Reddit): | |
| """Container for a user's profile page. As such, the Account | |
| object of the user must be passed in as the first argument, along | |
| with the current sub-page (to determine the title to be rendered | |
| on the page)""" | |
| searchbox = False | |
| create_reddit_box = False | |
| submit_box = False | |
| def __init__(self, user, *a, **kw): | |
| self.user = user | |
| Reddit.__init__(self, body_class = "profile_page", *a, **kw) | |
| def header_nav(self): | |
| path = "/user/%s/" % self.user.name | |
| main_buttons = [] | |
| # Only show the profile link if this user has a user page in the wiki | |
| wikipage = WikiPageCached({'url': WikiPageCached.get_url_for_user_page(self.user)}) | |
| if wikipage.success: | |
| main_buttons.append(NavButton(_('Profile'), '/', aliases = ['/profile'])) | |
| main_buttons += [ | |
| NavButton(menu.overview, '/overview'), | |
| NavButton(_('Comments'), 'comments'), | |
| NamedButton('submitted')] | |
| if votes_visible(self.user): | |
| main_buttons += [NamedButton('liked'), | |
| NamedButton('disliked'), | |
| NamedButton('hidden')] | |
| if c.user_is_loggedin and self.user._id == c.user._id: | |
| # User is looking at their own page | |
| main_buttons.append(NamedButton('drafts')) | |
| return NavMenu(main_buttons, base_path = path, title = _('View'), _id='nav', type='navlist') | |
| def rightbox(self): | |
| rb = Reddit.rightbox(self) | |
| if self.user != c.user: | |
| rb.push(ProfileBar(self.user)) | |
| rb.push(GoogleSearchForm(label="Search this user's posts & comments:", | |
| query_prefix='"author: ' + self.user.name + '" ')) | |
| return rb | |
| class ProfileBar(Wrapped): | |
| """Draws a right box for info about the user (karma, etc)""" | |
| def __init__(self, user, buttons = None): | |
| Wrapped.__init__(self, user = user, buttons = buttons) | |
| self.isFriend = self.user._id in c.user.friends \ | |
| if c.user_is_loggedin else False | |
| self.isMe = (self.user == c.user) | |
| class MenuArea(Wrapped): | |
| """Draws the gray box at the top of a page for sort menus""" | |
| def __init__(self, menus = []): | |
| Wrapped.__init__(self, menus = menus) | |
| class InfoBar(Wrapped): | |
| """Draws the yellow box at the top of a page for info""" | |
| def __init__(self, message = ''): | |
| Wrapped.__init__(self, message = message) | |
| class UnfoundPage(Wrapped): | |
| """Wrapper for the 404 page""" | |
| def __init__(self, choice='a'): | |
| Wrapped.__init__(self, choice=choice) | |
| class ErrorPage(Wrapped): | |
| """Wrapper for an error message""" | |
| def __init__(self, message = _("You aren't allowed to do that.")): | |
| Wrapped.__init__(self, message = message) | |
| class Profiling(Wrapped): | |
| """Debugging template for code profiling using built in python | |
| library (only used in middleware)""" | |
| def __init__(self, header = '', table = [], caller = [], callee = [], path = ''): | |
| Wrapped.__init__(self, header = header, table = table, caller = caller, | |
| callee = callee, path = path) | |
| class Over18(Wrapped): | |
| """The creepy 'over 18' check page for nsfw content.""" | |
| pass | |
| class SubredditTopBar(Wrapped): | |
| """The horizontal strip at the top of most pages for navigating | |
| user-created reddits.""" | |
| def __init__(self): | |
| Wrapped.__init__(self) | |
| my_reddits = [] | |
| sr_ids = Subreddit.user_subreddits(c.user if c.user_is_loggedin else None) | |
| if sr_ids: | |
| my_reddits = Subreddit._byID(sr_ids, True, | |
| return_dict = False) | |
| my_reddits.sort(key = lambda sr: sr.name.lower()) | |
| drop_down_buttons = [] | |
| for sr in my_reddits: | |
| drop_down_buttons.append(SubredditButton(sr)) | |
| #leaving the 'home' option out for now | |
| #drop_down_buttons.insert(0, NamedButton('home', sr_path = False, | |
| # css_class = 'top-option', | |
| # dest = '/')) | |
| drop_down_buttons.append(NamedButton('edit', sr_path = False, | |
| css_class = 'bottom-option', | |
| dest = '/categories/')) | |
| self.sr_dropdown = SubredditMenu(drop_down_buttons, | |
| title = _('My categories'), | |
| type = 'srdrop') | |
| pop_reddits = Subreddit.default_srs(c.content_langs, limit = 30) | |
| buttons = [] | |
| for sr in c.recent_reddits: | |
| # Extra guarding for Issue #50 | |
| if hasattr(sr, 'name'): | |
| buttons.append(SubredditButton(sr)) | |
| for sr in pop_reddits: | |
| if sr not in c.recent_reddits: | |
| buttons.append(SubredditButton(sr)) | |
| self.sr_bar = NavMenu(buttons, type='flatlist', separator = '-', | |
| _id = 'sr-bar') | |
| class SubredditBox(Wrapped): | |
| """A content pane that has the lists of subreddits that go in the | |
| right pane by default""" | |
| def __init__(self): | |
| Wrapped.__init__(self) | |
| self.title = _('Other reddit communities') | |
| self.subtitle = 'Visit your subscribed categories (in bold) or explore new ones' | |
| self.create_link = ('/categories/', menu.more) | |
| self.more_link = ('/categories/create', _('Create')) | |
| my_reddits = [] | |
| sr_ids = Subreddit.user_subreddits(c.user if c.user_is_loggedin else None) | |
| if sr_ids: | |
| my_reddits = Subreddit._byID(sr_ids, True, | |
| return_dict = False) | |
| my_reddits.sort(key = lambda sr: sr._downs, reverse = True) | |
| display_reddits = my_reddits[:g.num_side_reddits] | |
| #remove the current reddit | |
| display_reddits = filter(lambda x: x != c.site, display_reddits) | |
| pop_reddits = Subreddit.default_srs(c.content_langs, limit = g.num_side_reddits) | |
| #add english reddits to the list | |
| if c.content_langs != 'all' and 'en' not in c.content_langs: | |
| en_reddits = Subreddit.default_srs(['en']) | |
| pop_reddits += [sr for sr in en_reddits if sr not in pop_reddits] | |
| for sr in pop_reddits: | |
| if len(display_reddits) >= g.num_side_reddits: | |
| break | |
| if sr != c.site and sr not in display_reddits: | |
| display_reddits.append(sr) | |
| col1, col2 = [], [] | |
| cur_col, other = col1, col2 | |
| for sr in display_reddits: | |
| cur_col.append((sr, sr in my_reddits)) | |
| cur_col, other = other, cur_col | |
| self.cols = ((col1, col2)) | |
| self.mine = my_reddits | |
| class SubscriptionBox(Wrapped): | |
| """The list of reddits a user is currently subscribed to to go in | |
| the right pane.""" | |
| def __init__(self): | |
| sr_ids = Subreddit.user_subreddits(c.user if c.user_is_loggedin else None) | |
| srs = Subreddit._byID(sr_ids, True, return_dict = False) | |
| srs.sort(key = lambda sr: sr.name.lower()) | |
| b = IDBuilder([sr._fullname for sr in srs]) | |
| self.reddits = LinkListing(b).listing().things | |
| class CreateSubreddit(Wrapped): | |
| """reddit creation form.""" | |
| def __init__(self, site = None, name = '', listings = []): | |
| Wrapped.__init__(self, site = site, name = name, listings = listings) | |
| class SubredditStylesheet(Wrapped): | |
| """form for editing or creating subreddit stylesheets""" | |
| def __init__(self, site = None, | |
| stylesheet_contents = ''): | |
| Wrapped.__init__(self, site = site, | |
| stylesheet_contents = stylesheet_contents) | |
| class CssError(Wrapped): | |
| """Rendered error returned to the stylesheet editing page via ajax""" | |
| def __init__(self, error): | |
| # error is an instance of cssutils.py:ValidationError | |
| Wrapped.__init__(self, error = error) | |
| class UploadedImage(Wrapped): | |
| "The page rendered in the iframe during an upload of a header image" | |
| def __init__(self,status,img_src, name="", errors = {}): | |
| self.errors = list(errors.iteritems()) | |
| Wrapped.__init__(self, status=status, img_src=img_src, name = name) | |
| class ImageBrowser(Wrapped): | |
| "The page rendered in the tinyMCE image browser window" | |
| def __init__(self, article): | |
| self.article = article | |
| Wrapped.__init__(self) | |
| class Password(Wrapped): | |
| """Form encountered when 'recover password' is clicked in the LoginFormWide.""" | |
| def __init__(self, success=False): | |
| Wrapped.__init__(self, success = success) | |
| class PasswordReset(Wrapped): | |
| """Template for generating an email to the user who wishes to | |
| reset their password (step 2 of password recovery, after they have | |
| entered their user name in Password.)""" | |
| pass | |
| class ResetPassword(Wrapped): | |
| """Form for actually resetting a lost password, after the user has | |
| clicked on the link provided to them in the Password_Reset email | |
| (step 3 of password recovery.)""" | |
| pass | |
| class EmailVerify(Wrapped): | |
| """Form for providing a confirmation code to a new user.""" | |
| pass | |
| class WikiSignupFail(Wrapped): | |
| """Email template. Tells a user that their automatic wiki account | |
| creation failed. | |
| """ | |
| pass | |
| class WikiSignupNotification(Wrapped): | |
| """Email template. Tells a user their account on the LessWrong Wiki | |
| has been created. | |
| """ | |
| pass | |
| class WikiAPIError(Wrapped): | |
| """Email template to notify devs of unknown account creation errors.""" | |
| pass | |
| class WikiUserExists(Wrapped): | |
| """Email template to tell a user that we tried to make their account | |
| but someone else already had it. | |
| """ | |
| pass | |
| class WikiIncompatibleName(Wrapped): | |
| """Email template to tell them their username doesn't allow automatic | |
| wikification. | |
| """ | |
| pass | |
| class Captcha(Wrapped): | |
| """Container for rendering robot detection device.""" | |
| def __init__(self, error=None, tabular=True, label=True): | |
| self.error = _('Try entering those letters again') if error else "" | |
| self.iden = get_captcha() | |
| Wrapped.__init__(self, tabular=tabular, label=label) | |
| class CommentReplyBox(Wrapped): | |
| """Used on LinkInfoPage to render the comment reply form at the | |
| top of the comment listing as well as the template for the forms | |
| which are JS inserted when clicking on 'reply' in either a comment | |
| or message listing.""" | |
| def __init__(self, link_name='', captcha=None, action = 'comment'): | |
| Wrapped.__init__(self, link_name = link_name, captcha = captcha, | |
| action = action) | |
| class CommentListing(Wrapped): | |
| """Comment heading and sort, limit options""" | |
| def __init__(self, content, num_comments, nav_menus = []): | |
| Wrapped.__init__(self, content=content, num_comments=num_comments, menus = nav_menus) | |
| class PermalinkMessage(Wrapped): | |
| """renders the box on comment pages that state 'you are viewing a | |
| single comment's thread'""" | |
| def __init__(self, comments_url, has_more_comments=False): | |
| self.comments_url = comments_url | |
| self.has_more_comments = has_more_comments | |
| Wrapped.__init__(self) | |
| class PaneStack(Wrapped): | |
| """Utility class for storing and rendering a list of block elements.""" | |
| def __init__(self, panes=[], div_id = None, css_class=None, div=False): | |
| div = div or div_id or css_class or False | |
| self.div_id = div_id | |
| self.css_class = css_class | |
| self.div = div | |
| self.stack = list(panes) | |
| Wrapped.__init__(self) | |
| def append(self, item): | |
| """Appends an element to the end of the current stack""" | |
| self.stack.append(item) | |
| def push(self, item): | |
| """Prepends an element to the top of the current stack""" | |
| self.stack.insert(0, item) | |
| def insert(self, *a): | |
| """inerface to list.insert on the current stack""" | |
| return self.stack.insert(*a) | |
| @property | |
| def empty(self): | |
| """Return True if the stack has any items, False otherwise""" | |
| return len(self.stack) == 0 | |
| class SearchForm(Wrapped): | |
| """The simple search form in the header of the page. prev_search | |
| is the previous search.""" | |
| def __init__(self, prev_search = ''): | |
| Wrapped.__init__(self, prev_search = prev_search) | |
| class GoogleSearchForm(Wrapped): | |
| """Shows Google Custom Search box""" | |
| def __init__(self, label='', query_prefix='', query_suffix=''): | |
| Wrapped.__init__(self, label=label, query_prefix=query_prefix, query_suffix=query_suffix) | |
| class WikiPageList(Wrapped): | |
| """Shows Wiki Page List box""" | |
| def __init__(self, link): | |
| if link: | |
| self.articleurl = link.url | |
| else: | |
| self.articleurl = None | |
| Wrapped.__init__(self) | |
| class GoogleSearchResultsFrame(Wrapped): | |
| """Shows Google Custom Search box""" | |
| def __init__(self): | |
| Wrapped.__init__(self) | |
| class GoogleSearchResults(BoringPage): | |
| """Receieves search results from Google""" | |
| def __init__(self, pagename, *a, **kw): | |
| kw['content'] = GoogleSearchResultsFrame() | |
| BoringPage.__init__(self, pagename, robots='noindex', *a, **kw) | |
| def content(self): | |
| return self.content_stack(self._content) | |
| # return self.content_stack(self.infobar, | |
| # self.nav_menu, self._content) | |
| class ArticleNavigation(Wrapped): | |
| """Generates article navigation fragment for the supplied link""" | |
| def __init__(self, link, author): | |
| Wrapped.__init__(self, article=link, author=author) | |
| class SearchBar(Wrapped): | |
| """More detailed search box for /search and /categories pages. | |
| Displays the previous search as well as info of the elapsed_time | |
| and num_results if any.""" | |
| def __init__(self, num_results = 0, prev_search = '', elapsed_time = 0, **kw): | |
| # not listed explicitly in args to ensure it translates properly | |
| self.header = kw.get('header', _("Previous search")) | |
| self.prev_search = prev_search | |
| self.elapsed_time = elapsed_time | |
| # All results are approximate unless there are fewer than 10. | |
| if num_results > 10: | |
| self.num_results = (num_results / 10) * 10 | |
| else: | |
| self.num_results = num_results | |
| Wrapped.__init__(self) | |
| class Frame(Wrapped): | |
| """Frameset for the FrameToolbar used when a user hits /goto and | |
| has pref_toolbar set. The top 30px of the page are dedicated to | |
| the toolbar, while the rest of the page will show the results of | |
| following the link.""" | |
| def __init__(self, url='', title='', fullname=''): | |
| Wrapped.__init__(self, url = url, title = title, fullname = fullname) | |
| class FrameToolbar(Wrapped): | |
| """The reddit voting toolbar used together with Frame.""" | |
| extension_handling = False | |
| def __init__(self, link = None, **kw): | |
| self.title = link.title | |
| Wrapped.__init__(self, link = link, *kw) | |
| class NewLink(Wrapped): | |
| """Render the link submission form""" | |
| def __init__(self, captcha = None, | |
| article = '', | |
| title= '', | |
| subreddits = (), | |
| tags = (), | |
| sr_id = None, | |
| tab = 'article'): | |
| self.tabs = Tabs() | |
| self.tabs.add_tab('Article', 'article-field-pane', tab == 'article') | |
| self.tabs.add_tab('Link', 'link-field-pane', tab == 'link') | |
| self.editing = False | |
| Wrapped.__init__(self, captcha = captcha, article = article, | |
| title = title, subreddits = subreddits, tags = tags, | |
| sr_id = sr_id, notify_on_comment = True, | |
| cc_licensed = True) | |
| class EditLink(Wrapped): | |
| """Render the edit link form""" | |
| def __init__(self, article, subreddits, tags, captcha): | |
| self.editing = True | |
| self.tabs = Tabs() | |
| if article.is_self: | |
| self.tabs.add_tab('Article', 'article-field-pane') | |
| else: | |
| self.tabs.add_tab('Link', 'link-field-pane') | |
| Wrapped.__init__(self, article, captcha = captcha, | |
| subreddits = subreddits, tags = tags) | |
| class ShareLink(Wrapped): | |
| def __init__(self, link_name = "", emails = None): | |
| captcha = Captcha() if c.user.needs_captcha() else None | |
| Wrapped.__init__(self, link_name = link_name, | |
| emails = c.user.recent_share_emails(), | |
| captcha = captcha) | |
| class Share(Wrapped): | |
| pass | |
| class Mail_Opt(Wrapped): | |
| pass | |
| class OptOut(Wrapped): | |
| pass | |
| class OptIn(Wrapped): | |
| pass | |
| # class UserStats(Wrapped): | |
| # """For drawing the stats page, which is fetched from the cache.""" | |
| # def __init__(self): | |
| # Wrapped.__init__(self) | |
| # cache_stats = cache.get('stats') | |
| # if cache_stats: | |
| # top_users, top_day, top_week = cache_stats | |
| # #lookup user objs | |
| # uids = [] | |
| # uids.extend(u for u in top_users) | |
| # uids.extend(u[0] for u in top_day) | |
| # uids.extend(u[0] for u in top_week) | |
| # users = Account._byID(uids, data = True) | |
| # self.top_users = (users[u] for u in top_users) | |
| # self.top_day = ((users[u[0]], u[1]) for u in top_day) | |
| # self.top_week = ((users[u[0]], u[1]) for u in top_week) | |
| # else: | |
| # self.top_users = self.top_day = self.top_week = () | |
| class ButtonEmbed(Wrapped): | |
| """Generates the JS wrapper around the buttons for embedding.""" | |
| def __init__(self, button = None, width = 100, height=100, referer = "", url = ""): | |
| Wrapped.__init__(self, button = button, width = width, height = height, | |
| referer=referer, url = url) | |
| class ButtonLite(Wrapped): | |
| """Generates the JS wrapper around the buttons for embedding.""" | |
| def __init__(self, image = None, link = None, url = "", styled = True, target = '_top'): | |
| Wrapped.__init__(self, image = image, link = link, url = url, styled = styled, target = target) | |
| class Button(Wrapped): | |
| """the voting buttons, embedded with the ButtonEmbed wrapper, shown on /buttons""" | |
| extension_handling = False | |
| def __init__(self, link = None, button = None, css=None, | |
| url = None, title = '', score_fmt = None, vote = True, target = "_parent", | |
| bgcolor = None, width = 100): | |
| Wrapped.__init__(self, link = link, score_fmt = score_fmt, | |
| likes = link.likes if link else None, | |
| button = button, css = css, url = url, title = title, | |
| vote = vote, target = target, bgcolor=bgcolor, width=width) | |
| class ButtonNoBody(Button): | |
| """A button page that just returns the raw button for direct embeding""" | |
| pass | |
| class ButtonDemoPanel(Wrapped): | |
| """The page for showing the different styles of embedable voting buttons""" | |
| pass | |
| class Feedback(Wrapped): | |
| """The feedback and ad inquery form(s)""" | |
| def __init__(self, captcha=None, title=None, action='/feedback', | |
| message='', name='', email='', replyto='', success = False): | |
| Wrapped.__init__(self, captcha = captcha, title = title, action = action, | |
| message = message, name = name, email = email, replyto = replyto, | |
| success = success) | |
| class WidgetDemoPanel(Wrapped): | |
| """Demo page for the .embed widget.""" | |
| pass | |
| class Socialite(Wrapped): | |
| """Demo page for the socialite Firefox extension""" | |
| pass | |
| class Bookmarklets(Wrapped): | |
| """The bookmarklets page.""" | |
| def __init__(self, buttons=["reddit", "like", "dislike", | |
| "save", "serendipity!"]): | |
| Wrapped.__init__(self, buttons = buttons) | |
| class AdminTranslations(Wrapped): | |
| """The translator control interface, used for determining which | |
| user is allowed to edit which translation file and for providing a | |
| summary of what translation files are done and/or in use.""" | |
| def __init__(self): | |
| from r2.lib.translation import list_translations | |
| Wrapped.__init__(self) | |
| self.translations = list_translations() | |
| class Embed(Wrapped): | |
| """wrapper for embedding /help into reddit as if it were not on a separate wiki.""" | |
| def __init__(self,content = ''): | |
| Wrapped.__init__(self, content = content) | |
| class Page_down(Wrapped): | |
| def __init__(self, **kw): | |
| message = kw.get('message', _("This feature is currently unavailable. Sorry")) | |
| Wrapped.__init__(self, message = message) | |
| # Classes for dealing with friend/moderator/contributor/banned lists | |
| # TODO: if there is time, we could roll these Ajaxed classes into the | |
| # JsonTemplates framework... | |
| class Ajaxed(): | |
| """Base class for allowing simple interaction of UserTableItem and | |
| UserItem classes to be edited via JS and AJax requests. In | |
| analogy with Wrapped, this class provides an interface for | |
| 'rendering' dictionary representations of the data which can be | |
| passed to the client via JSON over AJAX""" | |
| __slots__ = ['kind', 'action', 'data'] | |
| def __init__(self, kind, action): | |
| self._ajax = dict(kind=kind, | |
| action = None, | |
| data = {}) | |
| def for_ajax(self, action = None): | |
| self._ajax['action'] = action | |
| self._ajax['data'] = self.ajax_render() | |
| return self._ajax | |
| def ajax_render(self, style="html"): | |
| return {} | |
| class UserTableItem(Wrapped, Ajaxed): | |
| """A single row in a UserList of type 'type' and of name | |
| 'container_name' for a given user. The provided list of 'cells' | |
| will determine what order the different columns are rendered in. | |
| Currently, this list can consist of 'user', 'sendmessage' and | |
| 'remove'.""" | |
| def __init__(self, user, type, cellnames, container_name, editable): | |
| self.user, self.type, self.cells = user, type, cellnames | |
| self.container_name = container_name | |
| self.name = "tr_%s_%s" % (user.name, type) | |
| self.editable = editable | |
| Wrapped.__init__(self) | |
| Ajaxed.__init__(self, 'UserTable', 'add') | |
| def ajax_render(self, style="html"): | |
| """Generates a 'rendering' of this item suitable for | |
| processing by JS for insert or removal from an existing | |
| UserList""" | |
| cells = [] | |
| for cell in self.cells: | |
| r = Wrapped.part_render(self, 'cell_type', cell) | |
| cells.append(spaceCompress(r)) | |
| return dict(cells=cells, id=self.type, name=self.name) | |
| def __repr__(self): | |
| return '<UserTableItem "%s">' % self.user.name | |
| class UserList(Wrapped): | |
| """base class for generating a list of users""" | |
| form_title = '' | |
| table_title = '' | |
| type = '' | |
| container_name = '' | |
| cells = ('user', 'sendmessage', 'remove') | |
| _class = "" | |
| def __init__(self, editable = True): | |
| self.editable = editable | |
| Wrapped.__init__(self) | |
| def ajax_user(self, user): | |
| """Convenience method for constructing a UserTableItem | |
| instance of the user with type, container_name, etc. of this | |
| UserList instance""" | |
| return UserTableItem(user, self.type, self.cells, self.container_name, | |
| self.editable) | |
| @property | |
| def users(self, site = None): | |
| """Generates a UserTableItem wrapped list of the Account | |
| objects which should be present in this UserList.""" | |
| uids = self.user_ids() | |
| if uids: | |
| users = Account._byID(uids, True, return_dict = False) | |
| return [self.ajax_user(u) for u in users] | |
| else: | |
| return () | |
| def user_ids(self): | |
| """virtual method for fetching the list of ids of the Accounts | |
| to be listing in this UserList instance""" | |
| raise NotImplementedError | |
| @property | |
| def container_name(self): | |
| return c.site._fullname | |
| class FriendList(UserList): | |
| """Friend list on /pref/friends""" | |
| type = 'friend' | |
| @property | |
| def form_title(self): | |
| return _('Add a friend') | |
| @property | |
| def table_title(self): | |
| return _('Your friends') | |
| def user_ids(self): | |
| return c.user.friends | |
| @property | |
| def container_name(self): | |
| return c.user._fullname | |
| class ContributorList(UserList): | |
| """Contributor list on a restricted/private reddit.""" | |
| type = 'contributor' | |
| @property | |
| def form_title(self): | |
| return _('Add contributor') | |
| @property | |
| def table_title(self): | |
| return _("Contributors to %(reddit)s") % dict(reddit = c.site.name) | |
| def user_ids(self): | |
| return c.site.contributors | |
| class ModList(UserList): | |
| """Moderator list for a reddit.""" | |
| type = 'moderator' | |
| @property | |
| def form_title(self): | |
| return _('Add moderator') | |
| @property | |
| def table_title(self): | |
| return _("Moderators to %(reddit)s") % dict(reddit = c.site.name) | |
| def user_ids(self): | |
| return c.site.moderators | |
| class EditorList(UserList): | |
| """Editor list for a reddit.""" | |
| type = 'editor' | |
| @property | |
| def form_title(self): | |
| return _('Add editor') | |
| @property | |
| def table_title(self): | |
| return _("Editors of %(reddit)s") % dict(reddit = c.site.name) | |
| def user_ids(self): | |
| return c.site.editors | |
| class BannedList(UserList): | |
| """List of users banned from a given reddit""" | |
| type = 'banned' | |
| @property | |
| def form_title(self): | |
| return _('Ban users') | |
| @property | |
| def table_title(self): | |
| return _('Banned users') | |
| def user_ids(self): | |
| return c.site.banned | |
| class DetailsPage(LinkInfoPage): | |
| extension_handling= False | |
| def content(self): | |
| # TODO: a better way? | |
| from admin_pages import Details | |
| return self.content_stack(self.link_listing, Details(link = self.link)) | |
| class Cnameframe(Wrapped): | |
| """The frame page.""" | |
| def __init__(self, original_path, subreddit, sub_domain): | |
| Wrapped.__init__(self, original_path=original_path) | |
| if sub_domain and subreddit and original_path: | |
| self.title = "%s - %s" % (subreddit.title, sub_domain) | |
| u = UrlParser(subreddit.path + original_path) | |
| u.hostname = get_domain(cname = False, subreddit = False) | |
| u.update_query(**request.get.copy()) | |
| u.put_in_frame() | |
| self.frame_target = u.unparse() | |
| else: | |
| self.title = "" | |
| self.frame_target = None | |
| class PromotePage(Reddit): | |
| create_reddit_box = False | |
| submit_box = False | |
| extension_handling = False | |
| def __init__(self, title, nav_menus = None, *a, **kw): | |
| buttons = [NamedButton('current_promos', dest = ''), | |
| NamedButton('new_promo')] | |
| menu = NavMenu(buttons, title='show', base_path = '/promote', | |
| type='flatlist') | |
| if nav_menus: | |
| nav_menus.insert(0, menu) | |
| else: | |
| nav_menus = [menu] | |
| Reddit.__init__(self, title, nav_menus = nav_menus, *a, **kw) | |
| class PromotedLinks(Wrapped): | |
| def __init__(self, current_list, *a, **kw): | |
| self.things = current_list | |
| Wrapped.__init__(self, datefmt = datefmt, *a, **kw) | |
| class PromoteLinkForm(Wrapped): | |
| def __init__(self, sr = None, link = None, listing = '', | |
| timedeltatext = '', *a, **kw): | |
| Wrapped.__init__(self, sr = sr, link = link, | |
| datefmt = datefmt, | |
| timedeltatext = timedeltatext, | |
| listing = listing, | |
| *a, **kw) | |
| class FeedLinkBar(Wrapped): | |
| def __init__(self, request_path, *a, **kw): | |
| self.request_path = request_path | |
| Wrapped.__init__(self, *a, **kw) | |
| class AboutBox(Wrapped): pass | |
| class FeedBox(Wrapped): | |
| def __init__(self, feed_urls, *a, **kw): | |
| self.feed_urls = feed_urls | |
| Wrapped.__init__(self, *a, **kw) | |
| class RecentWikiEditsBox(Wrapped): | |
| def __init__(self, feed_url, *a, **kw): | |
| self.feed_url = feed_url | |
| Wrapped.__init__(self, *a, **kw) | |
| class SiteMeter(Wrapped): | |
| def __init__(self, codename, *a, **kw): | |
| self.codename = codename | |
| Wrapped.__init__(self, *a, **kw) | |
| class PollWrapper(Wrapped): | |
| def __init__(self, outer_thing, outer_body, voted_on_all, *a, **kw): | |
| Wrapped.__init__(self, *a, **kw) | |
| self.outer_thing = outer_thing | |
| self.outer_body = outer_body | |
| self.voted_on_all = voted_on_all | |
| class PollBallot(Wrapped): | |
| def __init__(self, poll, *a, **kw): | |
| self.poll = poll | |
| Wrapped.__init__(self, *a, **kw) | |
| class PollResults(Wrapped): | |
| def __init__(self, poll, *a, **kw): | |
| self.poll = poll | |
| Wrapped.__init__(self, *a, **kw) | |
| class MultipleChoicePollBallot(PollBallot): | |
| def __init__(self, poll, *a, **kw): | |
| PollBallot.__init__(self, poll, *a, **kw) | |
| class MultipleChoicePollResults(PollResults): | |
| def __init__(self, poll, *a, **kw): | |
| PollResults.__init__(self, poll, *a, **kw) | |
| class ScalePollBallot(PollBallot): | |
| def __init__(self, poll, *a, **kw): | |
| PollBallot.__init__(self, poll, *a, **kw) | |
| class ScalePollResults(PollResults): | |
| def __init__(self, poll, *a, **kw): | |
| PollResults.__init__(self, poll, *a, **kw) | |
| class ProbabilityPollBallot(PollBallot): | |
| def __init__(self, poll, *a, **kw): | |
| PollBallot.__init__(self, poll, *a, **kw) | |
| class ProbabilityPollResults(PollResults): | |
| def __init__(self, poll, *a, **kw): | |
| PollResults.__init__(self, poll, *a, **kw) | |
| class NumberPollBallot(PollBallot): | |
| def __init__(self, poll, *a, **kw): | |
| PollBallot.__init__(self, poll, *a, **kw) | |
| class NumberPollResults(PollResults): | |
| def __init__(self, poll, *a, **kw): | |
| PollResults.__init__(self, poll, *a, **kw) | |
| class UpcomingMeetups(SpaceCompressedWrapped): | |
| def __init__(self, location, max_distance, *a, **kw): | |
| meetups = Meetup.upcoming_meetups_near(location, max_distance, 2) | |
| Wrapped.__init__(self, meetups=meetups, location=location, *a, **kw) | |
| class MeetupsMap(Wrapped): | |
| def __init__(self, location, max_distance, *a, **kw): | |
| meetups = Meetup.upcoming_meetups_near(location, max_distance) | |
| Wrapped.__init__(self, meetups=meetups, location=location, *a, **kw) | |
| class NotEnoughKarmaToPost(Wrapped): | |
| pass | |
| class ShowMeetup(Wrapped): | |
| """docstring for ShowMeetup""" | |
| def __init__(self, meetup, **kw): | |
| # title_params = {'title':_force_unicode(meetup.title), 'site' : c.site.title} | |
| # title = strings.show_meetup_title % title_params | |
| Wrapped.__init__(self, meetup = meetup, **kw) | |
| class NewMeetup(Wrapped): | |
| def __init__(self, *a, **kw): | |
| Wrapped.__init__(self, *a, **kw) | |
| class EditMeetup(Wrapped): | |
| pass | |
| class MeetupIndexPage(Reddit): | |
| def __init__(self, **context): | |
| self.meetups = context.get("content", None) | |
| Reddit.__init__(self, **context) | |
| def content(self): | |
| return MeetupIndex(self.meetups) | |
| class MeetupIndex(Wrapped): | |
| def __init__(self, meetups = [], *a, **kw): | |
| self.meetups = meetups | |
| Wrapped.__init__(self, *a, **kw) | |
| def meetups(self): | |
| return self.meetups | |
| class MeetupNotification(Wrapped): pass | |
| class VirtualStudyRoom(Wrapped): pass | |
| class WikiPageInline(Wrapped): pass | |
| class WikiPage(Reddit): | |
| def __init__(self, name, page, skiplayout, **context): | |
| wikiPage = WikiPageCached(page) | |
| html = wikiPage.content() | |
| self.pagename = wikiPage.title() | |
| content = WikiPageInline(html=html, name=name, skiplayout=skiplayout, | |
| title=self.pagename, wiki_url=page['url']) | |
| Reddit.__init__(self, | |
| content = content, | |
| title = self.pagename, | |
| space_compress=False, | |
| **context) | |
| class TabModel(object): | |
| def __init__(self, title, pane_id, is_selected): | |
| self.title = title | |
| self.pane_id = pane_id | |
| self.is_selected = is_selected | |
| class Tabs(Wrapped): | |
| """Renders a list of tabs which can be clicked on. | |
| """ | |
| def __init__(self): | |
| self.tabs = [] | |
| def add_tab(self, title, pane_id, is_selected = False): | |
| # The first tab is always selected | |
| if len(self.tabs) == 0: | |
| is_selected = True | |
| # Ensure at most one tab is selected | |
| if is_selected: | |
| for tab in self.tabs: | |
| tab.is_selected = False | |
| self.tabs.append(TabModel(title, pane_id, is_selected)) | |
| def _get_tab(self, pane_id): | |
| for tab in self.tabs: | |
| if tab.pane_id == pane_id: | |
| return tab | |
| def has_tab(self, pane_id): | |
| tab = self._get_tab(pane_id) | |
| return tab is not None | |
| def is_selected(self, pane_id): | |
| tab = self._get_tab(pane_id) | |
| return tab is not None and tab.is_selected |