Files
reddit/r2/r2/controllers/listingcontroller.py
2013-03-07 16:22:39 -05:00

1058 lines
39 KiB
Python
Executable File

# The contents of this file are subject to the Common Public Attribution
# License Version 1.0. (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
# software over a computer network and provide for limited attribution for the
# Original Developer. In addition, Exhibit A has been modified to be consistent
# with Exhibit B.
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
# the specific language governing rights and limitations under the License.
#
# The Original Code is reddit.
#
# The Original Developer is the Initial Developer. The Initial Developer of
# the Original Code is reddit Inc.
#
# All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
# Inc. All Rights Reserved.
###############################################################################
from oauth2 import OAuth2ResourceController, require_oauth2_scope
from reddit_base import RedditController, base_listing
from r2.models import *
from r2.models.query_cache import CachedQuery, MergedCachedQuery
from r2.config.extensions import is_api
from r2.lib.pages import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import TimeMenu, SortMenu, RecSortMenu, ProfileSortMenu
from r2.lib.menus import ControversyTimeMenu
from r2.lib.rising import get_rising
from r2.lib.wrapped import Wrapped
from r2.lib.normalized_hot import normalized_hot, get_hot
from r2.lib.db.thing import Query, Merge, Relations
from r2.lib.db import queries
from r2.lib.strings import Score
from r2.lib import organic
import r2.lib.search as search
from r2.lib.template_helpers import add_sr
from r2.lib.utils import iters, check_cheating, timeago
from r2.lib import sup
from r2.lib.validator import *
import socket
from api_docs import api_doc, api_section
from pylons.i18n import _
from pylons.controllers.util import redirect_to
import random
from functools import partial
class ListingController(RedditController, OAuth2ResourceController):
"""Generalized controller for pages with lists of links."""
# toggle skipping of links based on the users' save/hide/vote preferences
skip = True
# allow stylesheets on listings
allow_stylesheets = True
# toggles showing numbers
show_nums = True
# any text that should be shown on the top of the page
infotext = None
# builder class to use to generate the listing. if none, we'll try
# to figure it out based on the query type
builder_cls = None
# page title
title_text = ''
# login box, subreddit box, submit box, etc, visible
show_sidebar = True
# class (probably a subclass of Reddit) to use to render the page.
render_cls = Reddit
#extra parameters to send to the render_cls constructor
render_params = {}
extra_page_classes = ['listing-page']
def pre(self):
self.check_for_bearer_token()
RedditController.pre(self)
@property
def menus(self):
"""list of menus underneat the header (e.g., sort, time, kind,
etc) to be displayed on this listing page"""
return []
def build_listing(self, num, after, reverse, count, **kwargs):
"""uses the query() method to define the contents of the
listing and renders the page self.render_cls(..).render() with
the listing as contents"""
self.num = num
self.count = count
self.after = after
self.reverse = reverse
self.query_obj = self.query()
self.builder_obj = self.builder()
self.listing_obj = self.listing()
content = self.content()
res = self.render_cls(content = content,
page_classes = self.extra_page_classes,
show_sidebar = self.show_sidebar,
nav_menus = self.menus,
title = self.title(),
robots = getattr(self, "robots", None),
**self.render_params).render()
return res
def content(self):
"""Renderable object which will end up as content of the render_cls"""
return self.listing_obj
def query(self):
"""Query to execute to generate the listing"""
raise NotImplementedError
def builder(self):
#store the query itself so it can be used elsewhere
if self.builder_cls:
builder_cls = self.builder_cls
elif isinstance(self.query_obj, Query):
builder_cls = QueryBuilder
elif isinstance(self.query_obj, search.SearchQuery):
builder_cls = SearchBuilder
elif isinstance(self.query_obj, iters):
builder_cls = IDBuilder
elif isinstance(self.query_obj, (queries.CachedResults, queries.MergedCachedResults)):
builder_cls = IDBuilder
elif isinstance(self.query_obj, (CachedQuery, MergedCachedQuery)):
builder_cls = IDBuilder
b = builder_cls(self.query_obj,
num = self.num,
skip = self.skip,
after = self.after,
count = self.count,
reverse = self.reverse,
keep_fn = self.keep_fn(),
wrap = self.builder_wrapper)
return b
def keep_fn(self):
def keep(item):
wouldkeep = item.keep_item(item)
if getattr(item, "promoted", None) is not None:
return False
if item._deleted and not c.user_is_admin:
return False
return wouldkeep
return keep
def listing(self):
"""Listing to generate from the builder"""
if (getattr(c.site, "_id", -1) == get_promote_srid() and
not c.user_is_sponsor):
abort(403, 'forbidden')
pane = LinkListing(self.builder_obj, show_nums = self.show_nums).listing()
# Indicate that the comment tree wasn't built for comments
for i in pane:
if hasattr(i, 'full_comment_path'):
i.child = None
return pane
def title(self):
"""Page <title>"""
return _(self.title_text) + " : " + c.site.name
def rightbox(self):
"""Contents of the right box when rendering"""
pass
builder_wrapper = staticmethod(default_thing_wrapper())
@require_oauth2_scope("read")
@base_listing
@api_doc(api_section.listings, extensions=['json', 'xml'])
def GET_listing(self, **env):
check_cheating('site')
return self.build_listing(**env)
listing_api_doc = partial(api_doc, section=api_section.listings, extends=ListingController.GET_listing)
class FixListing(object):
"""When sorting by hotness, computing a listing when the before/after
link has a hottness of 0 is very slow. This class avoids drawing
next/prev links when that will happen."""
fix_listing = True
def listing(self):
listing = ListingController.listing(self)
if not self.fix_listing:
return listing
#404 existing bad pages
if self.after and self.after._hot == 0:
self.abort404()
#don't draw next/prev links for
if listing.things:
if listing.things[-1]._hot == 0:
listing.next = None
if listing.things[0]._hot == 0:
listing.prev = None
return listing
class HotController(FixListing, ListingController):
where = 'hot'
extra_page_classes = ListingController.extra_page_classes + ['hot-page']
def make_requested_ad(self):
try:
link = Link._by_fullname(self.requested_ad, data=True)
except NotFound:
self.abort404()
if not (link.promoted and
(c.user_is_sponsor or
c.user_is_loggedin and link.author_id == c.user._id)):
self.abort403()
if not promote.is_live_on_sr(link, c.site.name):
self.abort403()
res = wrap_links([link._fullname], wrapper=self.builder_wrapper,
skip=False)
if res.things:
return res
def make_single_ad(self):
promo_tuples = promote.lottery_promoted_links(c.user, c.site, n=10)
b = CampaignBuilder(promo_tuples, wrap=self.builder_wrapper,
keep_fn=organic.keep_fresh_links, num=1, skip=True)
res = LinkListing(b, nextprev=False).listing()
if res.things:
return res
def make_spotlight(self):
"""Build the Spotlight.
The frontpage gets a Spotlight box that contains promoted and organic
links from the user's subscribed subreddits and promoted links targeted
to the frontpage. If the user has disabled ads promoted links will not
be shown.
The content of the Spotlight box is a bit tricky because a single
version of the frontpage is cached and displayed to all logged out
users. Because of the caching we must include many promoted links and
select one to display on the client side. Otherwise, each logged out
user would see the same promoted link and we would not get the desired
distribution of promoted link views. Most of the promoted links are
included as stubs to reduce the size of the page. When a promoted link
stub is selected by the lottery the full link is fetched and displayed.
There are only ~1000 cache resets per day so it is necessary to use
a large subset of the eligible promoted links when choosing stubs for
the Spotlight box. Using 100 stubs works great when there are fewer than
100 possible promoted links and allows room for growth.
"""
organic_fullnames = organic.organic_links(c.user)
promoted_links = []
# If prefs allow it, mix in promoted links and sr discovery content
if c.user.pref_show_sponsors or not c.user.gold:
if g.live_config['sr_discovery_links']:
organic_fullnames.extend(g.live_config['sr_discovery_links'])
n_promoted = 100
n_build = 10
promo_tuples = promote.sample_promoted_links(c.user, c.site,
n=n_promoted)
promo_tuples = sorted(promo_tuples,
key=lambda p: p.weight,
reverse=True)
promo_build = promo_tuples[:n_build]
promo_stub = promo_tuples[n_build:]
b = CampaignBuilder(promo_build,
wrap=self.builder_wrapper,
keep_fn=promote.is_promoted)
promoted_links = b.get_items()[0]
promoted_links.extend(promo_stub)
if not (organic_fullnames or promoted_links):
return None
random.shuffle(organic_fullnames)
organic_fullnames = organic_fullnames[:10]
b = IDBuilder(organic_fullnames,
wrap=self.builder_wrapper,
keep_fn=organic.keep_fresh_links,
skip=True)
organic_links = b.get_items()[0]
has_subscribed = c.user.has_subscribed
interestbar_prob = g.live_config['spotlight_interest_sub_p'
if has_subscribed else
'spotlight_interest_nosub_p']
interestbar = InterestBar(has_subscribed)
promotion_prob = 0.5 if c.user_is_loggedin else 1.
s = SpotlightListing(organic_links=organic_links,
promoted_links=promoted_links,
interestbar=interestbar,
interestbar_prob=interestbar_prob,
promotion_prob=promotion_prob,
max_num = self.listing_obj.max_num,
max_score = self.listing_obj.max_score).listing()
return s
def query(self):
#no need to worry when working from the cache
# TODO: just remove this then since we're always using the query cache
self.fix_listing = False
if isinstance(c.site, DefaultSR):
if c.user_is_loggedin:
srlimit = Subreddit.DEFAULT_LIMIT
over18 = c.user.has_subscribed and c.over18
else:
srlimit = g.num_default_reddits
over18 = False
sr_ids = Subreddit.user_subreddits(c.user,
limit=srlimit,
over18=over18)
return normalized_hot(sr_ids)
elif isinstance(c.site, MultiReddit):
return normalized_hot(c.site.kept_sr_ids, obey_age_limit=False)
else:
return c.site.get_links('hot', 'all')
def content(self):
# only send a spotlight listing for HTML rendering
if c.render_style == "html":
spotlight = None
show_sponsors = not (not c.user.pref_show_sponsors and c.user.gold)
show_organic = c.user.pref_organic
on_frontpage = isinstance(c.site, DefaultSR)
if self.requested_ad:
spotlight = self.make_requested_ad()
elif on_frontpage and show_organic:
spotlight = self.make_spotlight()
elif show_sponsors:
spotlight = self.make_single_ad()
if spotlight:
return PaneStack([spotlight, self.listing_obj],
css_class='spacer')
return self.listing_obj
def title(self):
return c.site.title
@require_oauth2_scope("read")
@listing_api_doc(uri='/hot')
def GET_listing(self, **env):
self.requested_ad = request.get.get('ad')
self.infotext = request.get.get('deleted') and strings.user_deleted
return ListingController.GET_listing(self, **env)
class NewController(ListingController):
where = 'new'
title_text = _('newest submissions')
extra_page_classes = ListingController.extra_page_classes + ['new-page']
def keep_fn(self):
def keep(item):
"""Avoid showing links that are too young, to give time
for things like the spam filter and thumbnail fetcher to
act on them before releasing them into the wild"""
wouldkeep = item.keep_item(item)
if item.promoted is not None:
return False
elif c.user_is_loggedin and (c.user_is_admin or
item.subreddit.is_moderator(c.user)):
# let admins and moderators see them regardless
return wouldkeep
elif wouldkeep and c.user_is_loggedin and c.user._id == item.author_id:
# also let the author of the link see them
return True
else:
# otherwise, fall back to the regular logic (don't
# show hidden links, etc)
return wouldkeep
return keep
def query(self):
return c.site.get_links('new', 'all')
def POST_listing(self, **env):
# Redirect to GET mode in case of any legacy requests
return self.redirect(request.fullpath)
@require_oauth2_scope("read")
@listing_api_doc(uri='/new')
def GET_listing(self, **env):
if request.params.get('sort') == 'rising':
return self.redirect(add_sr('/rising'))
return ListingController.GET_listing(self, **env)
class RisingController(NewController):
where = 'rising'
title_text = _('rising submissions')
extra_page_classes = ListingController.extra_page_classes + ['rising-page']
def query(self):
return get_rising(c.site)
class BrowseController(ListingController):
where = 'browse'
def keep_fn(self):
"""For merged time-listings, don't show items that are too old
(this can happen when mr_top hasn't run in a while)"""
if self.time != 'all' and c.default_sr:
oldest = timeago('1 %s' % (str(self.time),))
def keep(item):
return item._date > oldest and item.keep_item(item)
return keep
else:
return ListingController.keep_fn(self)
@property
def menus(self):
return [ControversyTimeMenu(default = self.time)]
def query(self):
return c.site.get_links(self.sort, self.time)
@validate(t = VMenu('sort', ControversyTimeMenu))
def POST_listing(self, sort, t, **env):
# VMenu validator will save the value of time before we reach this
# point. Now just redirect to GET mode.
return self.redirect(
request.fullpath + query_string(dict(sort=sort, t=t)))
@require_oauth2_scope("read")
@validate(t = VMenu('sort', ControversyTimeMenu))
@listing_api_doc(uri='/{sort}', uri_variants=['/top', '/controversial'])
def GET_listing(self, sort, t, **env):
self.sort = sort
if sort == 'top':
self.title_text = _('top scoring links')
self.extra_page_classes += ['top-page']
elif sort == 'controversial':
self.title_text = _('most controversial links')
self.extra_page_classes += ['controversial-page']
else:
# 'sort' is forced to top/controversial by routing.py,
# but in case something has gone wrong...
abort(404)
self.time = t
return ListingController.GET_listing(self, **env)
class RandomrisingController(ListingController):
where = 'randomrising'
title_text = _('you\'re really bored now, eh?')
def query(self):
links = get_rising(c.site)
if not links:
# just pull from the new page if the rising page isn't
# populated for some reason
links = c.site.get_links('new', 'all')
if isinstance(links, Query):
links._limit = 200
links = [x._fullname for x in links]
links = list(links)
random.shuffle(links)
return links
class ByIDController(ListingController):
title_text = _('API')
skip = False
def query(self):
return self.names
@require_oauth2_scope("read")
@validate(links = VByName("names", thing_cls = Link, multiple = True))
def GET_listing(self, links, **env):
if not links:
return self.abort404()
self.names = [l._fullname for l in links]
return ListingController.GET_listing(self, **env)
#class RecommendedController(ListingController):
# where = 'recommended'
# title_text = _('recommended for you')
#
# @property
# def menus(self):
# return [RecSortMenu(default = self.sort)]
#
# def query(self):
# return get_recommended(c.user._id, sort = self.sort)
#
# @validate(VUser(),
# sort = VMenu("controller", RecSortMenu))
# def GET_listing(self, sort, **env):
# self.sort = sort
# return ListingController.GET_listing(self, **env)
class UserController(ListingController):
render_cls = ProfilePage
show_nums = False
@property
def menus(self):
res = []
if (self.where in ('overview', 'submitted', 'comments')):
res.append(ProfileSortMenu(default = self.sort))
if self.sort not in ("hot", "new"):
res.append(TimeMenu(default = self.time))
if self.where == 'saved' and c.user.gold:
srnames = LinkSavesBySubreddit.get_saved_subreddits(self.vuser)
srnames += CommentSavesBySubreddit.get_saved_subreddits(self.vuser)
srnames = sorted(list(set(srnames)))
if len(srnames) > 1:
sr_buttons = [NavButton(_('all'), None, opt='sr',
css_class='primary')]
for srname in srnames:
sr_buttons.append(NavButton(srname, srname, opt='sr'))
base_path = request.path
sr_menu = NavMenu(sr_buttons, base_path=base_path,
title=_('filter by subreddit'),
type='lightdrop')
res.append(sr_menu)
return res
def title(self):
titles = {'overview': _("overview for %(user)s"),
'comments': _("comments by %(user)s"),
'submitted': _("submitted by %(user)s"),
'liked': _("liked by %(user)s"),
'disliked': _("disliked by %(user)s"),
'saved': _("saved by %(user)s"),
'hidden': _("hidden by %(user)s"),
'promoted': _("promoted by %(user)s")}
title = titles.get(self.where, _('profile for %(user)s')) \
% dict(user = self.vuser.name, site = c.site.name)
return title
# TODO: this might not be the place to do this
skip = True
def keep_fn(self):
# keep promotions off of profile pages.
def keep(item):
if self.where == 'promoted':
return bool(getattr(item, "promoted", None))
wouldkeep = True
# TODO: Consider a flag to disable this (and see below plus builder.py)
if item._deleted and not c.user_is_admin:
return False
if self.time != 'all':
wouldkeep = (item._date > utils.timeago('1 %s' % str(self.time)))
if c.user == self.vuser:
if not item.likes and self.where == 'liked':
return False
if item.likes is not False and self.where == 'disliked':
return False
if self.where == 'saved' and not item.saved:
return False
return wouldkeep and (getattr(item, "promoted", None) is None and
(self.where == "deleted" or
not getattr(item, "deleted", False)))
return keep
def query(self):
q = None
if self.where == 'overview':
self.check_modified(self.vuser, 'overview')
q = queries.get_overview(self.vuser, self.sort, self.time)
elif self.where == 'comments':
sup.set_sup_header(self.vuser, 'commented')
self.check_modified(self.vuser, 'commented')
q = queries.get_comments(self.vuser, self.sort, self.time)
elif self.where == 'submitted':
sup.set_sup_header(self.vuser, 'submitted')
self.check_modified(self.vuser, 'submitted')
q = queries.get_submitted(self.vuser, self.sort, self.time)
elif self.where in ('liked', 'disliked'):
sup.set_sup_header(self.vuser, self.where)
self.check_modified(self.vuser, self.where)
if self.where == 'liked':
q = queries.get_liked(self.vuser)
else:
q = queries.get_disliked(self.vuser)
elif self.where == 'hidden':
q = queries.get_hidden(self.vuser)
elif self.where == 'saved':
srname = request.get.get('sr')
if srname and c.user.gold:
try:
sr_id = Subreddit._by_name(srname)._id
except NotFound:
sr_id = None
else:
sr_id = None
q = queries.get_saved(self.vuser, sr_id)
elif c.user_is_sponsor and self.where == 'promoted':
q = queries.get_promoted_links(self.vuser._id)
if q is None:
return self.abort404()
return q
@validate(vuser = VExistingUname('username'),
sort = VMenu('sort', ProfileSortMenu, remember = False),
time = VMenu('t', TimeMenu, remember = False))
@listing_api_doc(section=api_section.users, uri='/user/{username}/{where}',
uri_variants=['/user/{username}/' + where for where in [
'overview', 'submitted', 'comments',
'liked', 'disliked', 'hidden', 'saved']])
def GET_listing(self, where, vuser, sort, time, **env):
self.where = where
self.sort = sort
self.time = time
# the validator will ensure that vuser is a valid account
if not vuser:
return self.abort404()
if self.sort in ('hot', 'new'):
self.time = 'all'
# hide spammers profile pages
if (not c.user_is_loggedin or
(c.user._id != vuser._id and not c.user_is_admin)) \
and vuser._spam:
return self.abort404()
if where in ('liked', 'disliked') and not votes_visible(vuser):
return self.abort403()
if (where in ('saved', 'hidden') and not
((c.user_is_loggedin and c.user._id == vuser._id) or
c.user_is_admin)):
return self.abort403()
check_cheating('user')
self.vuser = vuser
self.render_params = {'user' : vuser}
c.profilepage = True
if vuser.pref_hide_from_robots:
self.robots = 'noindex,nofollow'
return ListingController.GET_listing(self, **env)
@validate(vuser = VExistingUname('username'))
@api_doc(section=api_section.users, uri='/user/{username}/about',
extensions=['json'])
def GET_about(self, vuser):
"""Return information about the user, including karma and gold status."""
if not is_api() or not vuser:
return self.abort404()
return Reddit(content = Wrapped(vuser)).render()
def GET_saved_redirect(self):
if not c.user_is_loggedin:
abort(404)
dest = "/".join(("/user", c.user.name, "saved"))
extension = request.environ.get('extension')
if extension:
dest = ".".join((dest, extension))
query_string = request.environ.get('QUERY_STRING')
if query_string:
dest += "?" + query_string
return redirect_to(dest)
class MessageController(ListingController):
show_nums = False
render_cls = MessagePage
allow_stylesheets = False
# note: this intentionally replaces the listing-page class which doesn't
# conceptually fit for styling these pages.
extra_page_classes = ['messages-page']
@property
def show_sidebar(self):
if c.default_sr and not isinstance(c.site, (ModSR, MultiReddit)):
return False
return self.where in ("moderator", "multi")
@property
def menus(self):
if c.default_sr and self.where in ('inbox', 'messages', 'comments',
'selfreply', 'unread'):
buttons = (NavButton(_("all"), "inbox"),
NavButton(_("unread"), "unread"),
NavButton(plurals.messages, "messages"),
NavButton(_("comment replies"), 'comments'),
NavButton(_("post replies"), 'selfreply'))
return [NavMenu(buttons, base_path = '/message/',
default = 'inbox', type = "flatlist")]
elif not c.default_sr or self.where in ('moderator', 'multi'):
buttons = (NavButton(_("all"), "inbox"),
NavButton(_("unread"), "unread"))
return [NavMenu(buttons, base_path = '/message/moderator/',
default = 'inbox', type = "flatlist")]
return []
def title(self):
return _('messages') + ': ' + _(self.where)
def keep_fn(self):
def keep(item):
wouldkeep = item.keep_item(item)
# TODO: Consider a flag to disable this (and see above plus builder.py)
if (item._deleted or item._spam) and not c.user_is_admin:
return False
if item.author_id in c.user.enemies:
return False
# don't show user their own unread stuff
if ((self.where == 'unread' or self.subwhere == 'unread')
and (item.author_id == c.user._id or not item.new)):
return False
return wouldkeep
return keep
@staticmethod
def builder_wrapper(thing):
if isinstance(thing, Comment):
f = thing._fullname
w = Wrapped(thing)
w.render_class = Message
w.to_id = c.user._id
w.was_comment = True
w._fullname = f
else:
w = ListingController.builder_wrapper(thing)
return w
def builder(self):
if (self.where == 'messages' or
(self.where in ("moderator", "multi") and self.subwhere != "unread")):
root = c.user
message_cls = UserMessageBuilder
if self.where == "multi":
root = c.site
message_cls = MultiredditMessageBuilder
elif not c.default_sr:
root = c.site
message_cls = SrMessageBuilder
elif self.where == 'moderator' and self.subwhere != 'unread':
message_cls = ModeratorMessageBuilder
parent = None
skip = False
if self.message:
if self.message.first_message:
parent = Message._byID(self.message.first_message,
data=True)
else:
parent = self.message
elif c.user.pref_threaded_messages:
skip = (c.render_style == "html")
return message_cls(root,
wrap = self.builder_wrapper,
parent = parent,
skip = skip,
num = self.num,
after = self.after,
keep_fn = self.keep_fn(),
reverse = self.reverse)
return ListingController.builder(self)
def listing(self):
if (self.where == 'messages' and
(c.user.pref_threaded_messages or self.message)):
return Listing(self.builder_obj).listing()
pane = ListingController.listing(self)
# Indicate that the comment tree wasn't built for comments
for i in pane.things:
if i.was_comment:
i.child = None
return pane
def query(self):
if self.where == 'messages':
q = queries.get_inbox_messages(c.user)
elif self.where == 'comments':
q = queries.get_inbox_comments(c.user)
elif self.where == 'selfreply':
q = queries.get_inbox_selfreply(c.user)
elif self.where == 'inbox':
q = queries.get_inbox(c.user)
elif self.where == 'unread':
q = queries.get_unread_inbox(c.user)
elif self.where == 'sent':
q = queries.get_sent(c.user)
elif self.where == 'multi' and self.subwhere == 'unread':
q = queries.get_unread_subreddit_messages_multi(c.site.kept_sr_ids)
elif self.where == 'moderator' and self.subwhere == 'unread':
if c.default_sr:
srids = Subreddit.reverse_moderator_ids(c.user)
srs = [sr for sr in Subreddit._byID(srids, data=False,
return_dict=False)
if sr.is_moderator_with_perms(c.user, 'mail')]
q = queries.get_unread_subreddit_messages_multi(srs)
else:
q = queries.get_unread_subreddit_messages(c.site)
elif self.where in ('moderator', 'multi'):
if c.have_mod_messages and self.mark != 'false':
c.user.modmsgtime = False
c.user._commit()
# the query is handled by the builder on the moderator page
return
else:
return self.abort404()
if self.where != 'sent':
#reset the inbox
if c.have_messages and self.mark != 'false':
c.user.msgtime = False
c.user._commit()
return q
@require_oauth2_scope("privatemessages")
@validate(VUser(),
message = VMessageID('mid'),
mark = VOneOf('mark',('true','false')))
@listing_api_doc(section=api_section.messages,
uri='/message/{where}',
uri_variants=['/message/inbox', '/message/unread', '/message/sent'])
def GET_listing(self, where, mark, message, subwhere = None, **env):
if not (c.default_sr
or c.site.is_moderator_with_perms(c.user, 'mail')
or c.user_is_admin):
abort(403, "forbidden")
if isinstance(c.site, MultiReddit):
if not (c.user_is_admin or c.site.is_moderator(c.user)):
self.abort403()
self.where = "multi"
elif isinstance(c.site, ModSR) or not c.default_sr:
self.where = "moderator"
else:
self.where = where
self.subwhere = subwhere
if mark is not None:
self.mark = mark
elif is_api():
self.mark = 'false'
elif c.render_style and c.render_style == "xml":
self.mark = 'false'
else:
self.mark = 'true'
self.message = message
return ListingController.GET_listing(self, **env)
@validate(VUser(),
to = nop('to'),
subject = nop('subject'),
message = nop('message'),
success = nop('success'))
def GET_compose(self, to, subject, message, success):
captcha = Captcha() if c.user.needs_captcha() else None
content = MessageCompose(to = to, subject = subject,
captcha = captcha,
message = message,
success = success)
return MessagePage(content = content).render()
class RedditsController(ListingController):
render_cls = SubredditsPage
def title(self):
return _('reddits')
def keep_fn(self):
base_keep_fn = ListingController.keep_fn(self)
def keep(item):
return base_keep_fn(item) and (c.over18 or not item.over_18)
return keep
def query(self):
if self.where == 'banned' and c.user_is_admin:
reddits = Subreddit._query(Subreddit.c._spam == True,
sort = desc('_date'),
write_cache = True,
read_cache = True,
cache_time = 5 * 60)
else:
reddits = None
if self.where == 'new':
reddits = Subreddit._query( write_cache = True,
read_cache = True,
cache_time = 5 * 60)
reddits._sort = desc('_date')
else:
reddits = Subreddit._query( write_cache = True,
read_cache = True,
cache_time = 60 * 60)
reddits._sort = desc('_downs')
# Consider resurrecting when it is not the World Cup
#if c.content_langs != 'all':
# reddits._filter(Subreddit.c.lang == c.content_langs)
if g.domain != 'reddit.com':
# don't try to render special subreddits (like promos)
reddits._filter(Subreddit.c.author_id != -1)
if self.where == 'popular':
self.render_params = {"show_interestbar": True}
return reddits
@listing_api_doc(section=api_section.subreddits,
uri='/reddits/{where}',
uri_variants=['/reddits/popular', '/reddits/new', '/reddits/banned'])
def GET_listing(self, where, **env):
self.where = where
return ListingController.GET_listing(self, **env)
class MyredditsController(ListingController, OAuth2ResourceController):
render_cls = MySubredditsPage
def pre(self):
self.check_for_bearer_token()
ListingController.pre(self)
@property
def menus(self):
buttons = (NavButton(plurals.subscriber, 'subscriber'),
NavButton(getattr(plurals, "approved submitter"), 'contributor'),
NavButton(plurals.moderator, 'moderator'))
return [NavMenu(buttons, base_path = '/reddits/mine/',
default = 'subscriber', type = "flatlist")]
def title(self):
return _('reddits: ') + self.where
def query(self):
reddits = SRMember._query(SRMember.c._name == self.where,
SRMember.c._thing2_id == c.user._id,
#hack to prevent the query from
#adding it's own date
sort = (desc('_t1_ups'), desc('_t1_date')),
eager_load = True,
thing_data = True)
reddits.prewrap_fn = lambda x: x._thing1
return reddits
def content(self):
user = c.user if c.user_is_loggedin else None
num_subscriptions = len(Subreddit.reverse_subscriber_ids(user))
if self.where == 'subscriber' and num_subscriptions == 0:
message = strings.sr_messages['empty']
else:
message = strings.sr_messages.get(self.where)
stack = PaneStack()
if message:
stack.append(InfoBar(message=message))
stack.append(self.listing_obj)
return stack
def build_listing(self, after=None, **kwargs):
if after and isinstance(after, Subreddit):
after = SRMember._fast_query(after, c.user, self.where,
data=False).values()[0]
if after and not isinstance(after, SRMember):
abort(400, 'gimme a srmember')
return ListingController.build_listing(self, after=after, **kwargs)
@require_oauth2_scope("mysubreddits")
@validate(VUser())
@listing_api_doc(section=api_section.subreddits,
uri='/reddits/mine/{where}',
uri_variants=['/reddits/mine/subscriber', '/reddits/mine/contributor', '/reddits/mine/moderator'])
def GET_listing(self, where='subscriber', **env):
self.where = where
return ListingController.GET_listing(self, **env)
class CommentsController(ListingController):
title_text = _('comments')
def query(self):
return c.site.get_all_comments()
@require_oauth2_scope("read")
def GET_listing(self, **env):
c.profilepage = True
return ListingController.GET_listing(self, **env)
class GildedController(ListingController):
title_text = _("gilded comments")
def keep_fn(self):
def keep(item):
return item.gildings > 0 and not item._deleted and not item._spam
return keep
def query(self):
try:
return c.site.get_gilded_comments()
except NotImplementedError:
abort(404)
@require_oauth2_scope("read")
def GET_listing(self, **env):
c.profilepage = True
return ListingController.GET_listing(self, **env)