/about: Paginate banned and contibutors.

This commit is contained in:
Andre D
2014-01-23 20:11:16 -05:00
committed by Brian Simpson
parent d54e284b74
commit 9ecb228d53
18 changed files with 691 additions and 310 deletions

View File

@@ -160,6 +160,9 @@ def make_map():
connect('/about/:location', controller='front',
action='spamlisting',
requirements=dict(location='reports|spam|modqueue|unmoderated'))
connect('/about/:where', controller='userlistlisting',
requirements=dict(where='contributors|banned|wikibanned|wikicontributors|moderators'),
action='listing')
connect('/about/:location', controller='front', action='editreddit',
location='about')
connect('/comments', controller='comments', action='listing')
@@ -175,6 +178,8 @@ def make_map():
mc('/t/:timereddit/*rest', controller='redirect',
action='timereddit_redirect')
mc('/prefs/:where', controller='userlistlisting',
action='listing', requirements=dict(where='blocked|friends'))
mc('/prefs/:location', controller='forms', action='prefs',
location='options')

View File

@@ -45,11 +45,7 @@ api('morechildren', MoreCommentJsonTemplate)
api('reddit', RedditJsonTemplate)
api('panestack', PanestackJsonTemplate)
api('listing', ListingJsonTemplate)
api('modlist', UserListJsonTemplate)
api('userlist', UserListJsonTemplate)
api('contributorlist', UserListJsonTemplate)
api('bannedlist', UserListJsonTemplate)
api('friendlist', UserListJsonTemplate)
api('userlisting', UserListingJsonTemplate)
api('usertableitem', UserTableItemJsonTemplate)
api('account', AccountJsonTemplate)

View File

@@ -44,6 +44,7 @@ def load_controllers():
from listingcontroller import NewController
from listingcontroller import RisingController
from listingcontroller import BrowseController
from listingcontroller import UserListListingController
from listingcontroller import MessageController
from listingcontroller import RedditsController
from listingcontroller import ByIDController

View File

@@ -44,13 +44,21 @@ from r2.lib import hooks
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
from r2.lib.utils import query_string, timefromnow, randstr
from r2.lib.utils import timeago, tup
from r2.lib.pages import (EnemyList, FriendList, ContributorList, ModList,
BannedList, WikiBannedList, WikiMayContributeList,
BoringPage, FormPage, CssError, UploadedImage,
from r2.lib.pages import (BoringPage, FormPage, CssError, UploadedImage,
ClickGadget, UrlParser, WrappedUser)
from r2.lib.pages import FlairList, FlairCsv, FlairTemplateEditor, \
FlairSelector
from r2.lib.pages import PrefApps
from r2.lib.pages import (
BannedTableItem,
ContributorTableItem,
FriendTableItem,
InvitedModTableItem,
ModTableItem,
WikiBannedTableItem,
WikiMayContributeTableItem,
)
from r2.lib.pages.things import (
default_thing_wrapper,
hot_links_by_url_listing,
@@ -866,22 +874,28 @@ class ApiController(RedditController):
if type in ('banned', 'wikibanned'):
container.add_rel_note(type, friend, note)
cls = dict(friend=FriendList,
moderator=ModList,
moderator_invite=ModList,
contributor=ContributorList,
wikicontributor=WikiMayContributeList,
banned=BannedList, wikibanned=WikiBannedList).get(type)
userlist = cls()
row_cls = dict(friend=FriendTableItem,
moderator=ModTableItem,
moderator_invite=InvitedModTableItem,
contributor=ContributorTableItem,
wikicontributor=WikiMayContributeTableItem,
banned=BannedTableItem,
wikibanned=WikiBannedTableItem).get(type)
form.set_inputs(name = "")
if note:
form.set_inputs(note = "")
form.removeClass("edited")
form.set_html(".status:first", userlist.executed_message(type))
if new and cls:
user_row = userlist.user_row(type, friend)
jquery("." + type + "-table").show(
).find("table").insert_table_rows(user_row)
if new and row_cls:
new._thing2 = friend
user_row = row_cls(new)
form.set_html(".status:first", user_row.executed_message)
rev_types = ["moderator", "moderator_invite", "friend"]
index = 0 if user_row.type not in rev_types else -1
table = jquery("." + type + "-table").show().find("table")
table.insert_table_rows(user_row, index=index)
table.find(".notfound").hide()
if new:
notify_user_added(type, c.user, friend, container)

View File

@@ -651,42 +651,14 @@ class FrontController(RedditController):
return pane
def _edit_normal_reddit(self, location, created):
# moderator is either reddit's moderator or an admin
moderator_rel = c.user_is_loggedin and c.site.get_moderator(c.user)
is_moderator = c.user_is_admin or moderator_rel
is_unlimited_moderator = c.user_is_admin or (
moderator_rel and moderator_rel.is_superuser())
is_moderator_with_perms = lambda *perms: (
c.user_is_admin
or moderator_rel and all(moderator_rel.has_permission(perm)
for perm in perms))
if is_moderator_with_perms('config') and location == 'edit':
if (location == 'edit' and
(c.user_is_admin or c.site.is_moderator_with_perms(c.user, 'config'))):
pane = PaneStack()
if created == 'true':
pane.append(InfoBar(message=strings.sr_created))
c.allow_styles = True
c.site = Subreddit._byID(c.site._id, data=True, stale=False)
pane.append(CreateSubreddit(site=c.site))
elif location == 'moderators':
pane = ModList(editable=is_unlimited_moderator)
elif is_moderator_with_perms('access') and location == 'banned':
pane = BannedList(editable=is_moderator_with_perms('access'))
elif is_moderator_with_perms('wiki') and location == 'wikibanned':
pane = WikiBannedList(editable=is_moderator_with_perms('wiki'))
elif (is_moderator_with_perms('wiki')
and location == 'wikicontributors'):
pane = WikiMayContributeList(
editable=is_moderator_with_perms('wiki'))
elif (location == 'contributors' and
# On public reddits, only moderators can see the whitelist.
# On private reddits, all contributors can see each other.
(c.site.type != 'public' or
(c.user_is_loggedin and
(c.site.is_moderator_with_perms(c.user, 'access')
or c.user_is_admin)))):
pane = ContributorList(
editable=is_moderator_with_perms('access'))
elif (location == 'stylesheet'
and c.site.can_change_stylesheet(c.user)
and not g.css_killswitch):
@@ -706,17 +678,14 @@ class FrontController(RedditController):
c.site.stylesheet_contents)
pane = SubredditStylesheetSource(stylesheet_contents=stylesheet)
elif location == 'traffic' and (c.site.public_traffic or
(is_moderator or c.user.employee)):
(c.site.is_moderator(c.user) or c.user.employee)):
pane = trafficpages.SubredditTraffic()
elif (location == "about") and is_api():
return self.redirect(add_sr('about.json'), code=301)
else:
return self.abort404()
is_wiki_action = location in ['wikibanned', 'wikicontributors']
return EditReddit(content=pane,
show_wiki_actions=is_wiki_action,
location=location,
extension_handling=False).render()
@@ -1324,11 +1293,6 @@ class FormsController(RedditController):
infotext = None
if not location or location == 'options':
content = PrefOptions(done=request.GET.get('done'))
elif location == 'friends':
content = PaneStack()
infotext = strings.friends % Friends.path
content.append(FriendList())
content.append(EnemyList())
elif location == 'update':
if verified:
infotext = strings.email_verified

View File

@@ -29,7 +29,7 @@ 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.menus import ControversyTimeMenu, menu
from r2.lib.rising import get_rising
from r2.lib.wrapped import Wrapped
from r2.lib.normalized_hot import normalized_hot
@@ -115,6 +115,7 @@ class ListingController(RedditController):
show_chooser=self.show_chooser,
nav_menus=self.menus,
title=self.title(),
infotext=self.infotext,
robots=getattr(self, "robots", None),
**self.render_params).render()
@@ -1129,6 +1130,209 @@ class CommentsController(ListingController):
c.profilepage = True
return ListingController.GET_listing(self, **env)
class UserListListingController(ListingController):
builder_cls = UserListBuilder
allow_stylesheets = False
skip = False
@property
def infotext(self):
if self.where == 'friends':
return strings.friends % Friends.path
elif self.where == 'blocked':
return _("To block a user click 'block user' below a message"
" from a user you wish to block from messaging you.")
@property
def render_params(self):
params = {}
is_wiki_action = self.where in ["wikibanned", "wikicontributors"]
params["show_wiki_actions"] = is_wiki_action
return params
@property
def render_cls(self):
if self.where in ["friends", "blocked"]:
return PrefsPage
return Reddit
def moderator_wrap(self, rel, invited=False):
rel._permission_class = ModeratorPermissionSet
cls = ModTableItem if not invited else InvitedModTableItem
return cls(rel, editable=self.editable)
@property
def builder_wrapper(self):
if self.where == 'banned':
cls = BannedTableItem
elif self.where == 'moderators':
return self.moderator_wrap
elif self.where == 'wikibanned':
cls = WikiBannedTableItem
elif self.where == 'contributors':
cls = ContributorTableItem
elif self.where == 'wikicontributors':
cls = WikiMayContributeTableItem
elif self.where == 'friends':
cls = FriendTableItem
elif self.where == 'blocked':
cls = EnemyTableItem
return lambda rel : cls(rel, editable=self.editable)
def title(self):
return menu[self.where]
def rel(self):
if self.where in ['friends', 'blocked']:
return Friend
return SRMember
def name(self):
return self._names.get(self.where)
_names = {
'friends': 'friend',
'blocked': 'enemy',
'moderators': 'moderator',
'contributors': 'contributor',
'banned': 'banned',
'wikibanned': 'wikibanned',
'wikicontributors': 'wikicontributor',
}
def query(self):
rel = self.rel()
if self.where in ["friends", "blocked"]:
thing1_id = c.user._id
else:
thing1_id = c.site._id
reversed_types = ["friends", "moderators", "blocked"]
sort = desc if self.where not in reversed_types else asc
q = rel._query(rel.c._thing1_id == thing1_id,
rel.c._name == self.name(),
sort=sort('_date'),
data=True)
if self.jump_to_val:
thing2_id = self.user._id if self.user else None
q._filter(rel.c._thing2_id == thing2_id)
return q
def listing(self):
listing = self.listing_cls(self.builder_obj,
addable=self.editable,
show_jump_to=self.show_jump_to,
jump_to_value=self.jump_to_val,
show_not_found=self.show_not_found,
nextprev=self.paginated,
has_add_form=self.editable)
return listing.listing()
def invited_mod_listing(self):
query = SRMember._query(SRMember.c._name == 'moderator_invite',
SRMember.c._thing1_id == c.site._id,
sort=asc('_date'), data=True)
wrapper = lambda rel: self.moderator_wrap(rel, invited=True)
b = self.builder_cls(query,
keep_fn=self.keep_fn(),
wrap=wrapper,
skip=False,
num=0)
return InvitedModListing(b, nextprev=False).listing()
def content(self):
is_api = c.render_style in extensions.API_TYPES
if self.where == 'moderators' and self.editable and not is_api:
# Do not stack the invited mod list in api mode
# to allow for api compatibility with older api users.
content = PaneStack()
content.append(self.listing_obj)
content.append(self.invited_mod_listing())
elif self.where == 'friends' and is_api:
content = PaneStack()
content.append(self.listing_obj)
empty_builder = IDBuilder([])
# Append an empty UserList on the api for backwards
# compatibility with the old blocked list.
content.append(UserListing(empty_builder, nextprev=False).listing())
else:
content = self.listing_obj
return content
@require_oauth2_scope("read")
@validate(user=VAccountByName('user'))
@base_listing
def GET_listing(self, where, user=None, **kw):
allow_on_fake_sr = ["blocked", "friends"]
if isinstance(c.site, FakeSubreddit) and not where in allow_on_fake_sr:
return self.abort404()
self.where = where
has_mod_access = ((c.user_is_loggedin and
c.site.is_moderator_with_perms(c.user, 'access'))
or c.user_is_admin)
if not c.user_is_loggedin and where not in ['contributors', 'moderators']:
abort(403)
self.listing_cls = None
self.editable = True
self.paginated = True
self.jump_to_val = request.GET.get('user')
self.show_not_found = bool(self.jump_to_val)
if where == 'contributors':
# On public reddits, only moderators may see the whitelist.
if c.site.type == 'public' and not has_mod_access:
abort(403)
# Used for subreddits like /r/lounge
if c.site.hide_subscribers:
abort(403)
self.listing_cls = ContributorListing
self.editable = has_mod_access
elif where == 'banned':
if not has_mod_access:
abort(403)
self.listing_cls = BannedListing
elif where == 'wikibanned':
if not c.site.is_moderator_with_perms(c.user, 'wiki'):
abort(403)
self.listing_cls = WikiBannedListing
elif where == 'wikicontributors':
if not c.site.is_moderator_with_perms(c.user, 'wiki'):
abort(403)
self.listing_cls = WikiMayContributeListing
elif where == 'moderators':
self.editable = ((c.user_is_loggedin and
c.site.is_unlimited_moderator(c.user)) or
c.user_is_admin)
self.listing_cls = ModListing
self.paginated = False
elif where == 'friends':
self.listing_cls = FriendListing
self.paginated = False
elif where == 'blocked':
self.listing_cls = EnemyListing
self.paginated = False
self.show_not_found = True
if not self.listing_cls:
abort(404)
self.user = user
self.show_jump_to = self.paginated
if not self.paginated:
kw['num'] = 0
check_cheating('site')
return self.build_listing(**kw)
class GildedController(ListingController):
title_text = _("gilded comments")

View File

@@ -688,6 +688,15 @@ class ListingJsonTemplate(ThingJsonTemplate):
def kind(self, wrapped):
return "Listing"
class UserListingJsonTemplate(ListingJsonTemplate):
def raw_data(self, thing):
if not thing.nextprev:
return {"children": self.rendered_data(thing)}
return ListingJsonTemplate.raw_data(self, thing)
def kind(self, wrapped):
return "Listing" if wrapped.nextprev else "UserList"
class UserListJsonTemplate(ThingJsonTemplate):
_data_attrs_ = dict(
children="users",
@@ -722,7 +731,7 @@ class UserTableItemJsonTemplate(ThingJsonTemplate):
(c.user.gold and thing.type == "friend")):
d["note"] = getattr(thing.rel, 'note', '')
if thing.type == "moderator":
permissions = thing.rel.permissions.iteritems()
permissions = thing.permissions.items()
d["mod_permissions"] = [perm for perm, has in permissions if has]
return d

View File

@@ -99,6 +99,7 @@ menu = MenuHandler(hot = _('hot'),
apps = _("apps"),
feeds = _("RSS feeds"),
friends = _("friends"),
blocked = _("blocked"),
update = _("password/email"),
delete = _("delete"),
otp = _("two-factor authentication"),

View File

@@ -866,10 +866,10 @@ class PrefsPage(Reddit):
extension_handling = False
def __init__(self, show_sidebar = False, *a, **kw):
def __init__(self, show_sidebar = False, title=None, *a, **kw):
title = title or "%s (%s)" % (_("preferences"), c.site.name.strip(' '))
Reddit.__init__(self, show_sidebar = show_sidebar,
title = "%s (%s)" %(_("preferences"),
c.site.name.strip(' ')),
title=title,
*a, **kw)
def build_toolbars(self):
@@ -880,6 +880,7 @@ class PrefsPage(Reddit):
buttons.append(NamedButton('feeds'))
buttons.extend([NamedButton('friends'),
NamedButton('blocked'),
NamedButton('update')])
if c.user_is_loggedin and c.user.name in g.admins:
@@ -2908,27 +2909,117 @@ class WrappedUser(CachedTemplate):
fullname = user._fullname,
user_deleted = user._deleted)
# Classes for dealing with friend/moderator/contributor/banned lists
class UserTableItem(Templated):
"""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."""
def __init__(self, user, type, cellnames, container_name, editable,
remove_action, rel=None):
type = ''
remove_action = 'unfriend'
cells = ('user', 'sendmessage', 'remove')
@property
def executed_message(self):
return _("added")
def __init__(self, user, editable=True, **kw):
self.user = user
self.type = type
self.cells = cellnames
self.rel = rel
self.container_name = container_name
self.editable = editable
self.remove_action = remove_action
Templated.__init__(self)
self.editable = editable
Templated.__init__(self, **kw)
def __repr__(self):
return '<UserTableItem "%s">' % self.user.name
class TrafficTableItem(UserTableItem):
type = "traffic_viewer"
remove_action = "rm_traffic_viewer"
@property
def container_name(self):
return self.link._fullname
class RelTableItem(UserTableItem):
def __init__(self, rel, **kw):
self._id = rel._id
self.rel = rel
UserTableItem.__init__(self, rel._thing2, **kw)
@property
def container_name(self):
return c.site._fullname
class FriendTableItem(RelTableItem):
type = 'friend'
@property
def cells(self):
if c.user.gold:
return ('user', 'sendmessage', 'note', 'age', 'remove')
return ('user', 'sendmessage', 'remove')
@property
def container_name(self):
return c.user._fullname
class EnemyTableItem(RelTableItem):
type = 'enemy'
cells = ('user', 'remove')
@property
def container_name(self):
return c.user._fullname
class BannedTableItem(RelTableItem):
type = 'banned'
cells = ('user', 'sendmessage', 'remove', 'note')
@property
def executed_message(self):
return _("banned")
class WikiBannedTableItem(BannedTableItem):
type = 'wikibanned'
class ContributorTableItem(RelTableItem):
type = 'contributor'
class WikiMayContributeTableItem(RelTableItem):
type = 'wikicontributor'
class InvitedModTableItem(RelTableItem):
type = 'moderator_invite'
cells = ('user', 'permissions', 'permissionsctl')
@property
def executed_message(self):
return _("invited")
def is_editable(self, user):
if not c.user_is_loggedin:
return False
elif c.user_is_admin:
return True
return c.site.is_unlimited_moderator(c.user)
def __init__(self, rel, editable=True, **kw):
if editable:
self.cells += ('remove',)
editable = self.is_editable(rel._thing2)
self.permissions = ModeratorPermissions(rel._thing2, self.type,
rel.get_permissions(),
editable=editable)
RelTableItem.__init__(self, rel, editable=editable, **kw)
class ModTableItem(InvitedModTableItem):
type = 'moderator'
@property
def executed_message(self):
return _("added")
def is_editable(self, user):
if not c.user_is_loggedin:
return False
elif c.user_is_admin:
return True
return c.user != user and c.site.can_demod(c.user, user)
class FlairPane(Templated):
def __init__(self, num, after, reverse, name, user):
# Make sure c.site isn't stale before rendering.
@@ -3205,11 +3296,7 @@ class UserList(Templated):
Templated.__init__(self)
def user_row(self, row_type, user, editable=True):
"""Convenience method for constructing a UserTableItem
instance of the user with type, container_name, etc. of this
UserList instance"""
return UserTableItem(user, row_type, self.cells, self.container_name,
editable, self.remove_action)
raise NotImplementedError
def _user_rows(self, row_type, uids, editable_fn=None):
"""Generates a UserTableItem wrapped list of the Account
@@ -3242,216 +3329,6 @@ class UserList(Templated):
def executed_message(self, row_type):
return _("added")
class FriendList(UserList):
"""Friend list on /pref/friends"""
type = 'friend'
def __init__(self, editable = True):
if c.user.gold:
self.friend_rels = c.user.friend_rels()
self.cells = ('user', 'sendmessage', 'note', 'age', 'remove')
self._class = "gold-accent rounded"
self.table_headers = (_('user'), '', _('note'), _('friendship'), '')
UserList.__init__(self)
@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
def user_row(self, row_type, user, editable=True):
if not getattr(self, "friend_rels", None):
return UserList.user_row(self, row_type, user, editable)
else:
rel = self.friend_rels[user._id]
return UserTableItem(user, row_type, self.cells, self.container_name,
editable, self.remove_action, rel)
@property
def container_name(self):
return c.user._fullname
class EnemyList(UserList):
"""Blacklist on /pref/friends"""
type = 'enemy'
cells = ('user', 'remove')
def __init__(self, editable=True, addable=False):
UserList.__init__(self, editable, addable)
@property
def table_title(self):
return _('blocked users')
def user_ids(self):
return c.user.enemies
@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 approved submitter")
@property
def table_title(self):
return _("approved submitters for %(reddit)s") % dict(reddit = c.site.name)
def user_ids(self):
if c.site.hide_subscribers:
return [] # /r/lounge has too many subscribers to load without timing out,
# and besides, some people might not want this list to be so
# easily accessible.
else:
return c.site.contributors
class ModList(UserList):
"""Moderator list for a reddit."""
type = 'moderator'
invite_type = 'moderator_invite'
invite_action = 'accept_moderator_invite'
form_title = _('add moderator')
invite_form_title = _('invite moderator')
remove_self_title = _('you are a moderator of this subreddit. %(action)s')
def __init__(self, editable=True):
super(ModList, self).__init__(editable=editable)
self.perms_by_type = {
self.type: c.site.moderators_with_perms(),
self.invite_type: c.site.moderator_invites_with_perms(),
}
self.cells = ('user', 'permissions', 'permissionsctl')
if editable:
self.cells += ('remove',)
@property
def table_title(self):
return _("moderators of /r/%(reddit)s") % {"reddit": c.site.name}
def executed_message(self, row_type):
if row_type == "moderator_invite":
return _("invited")
else:
return _("added")
@property
def can_force_add(self):
return c.user_is_admin
@property
def can_remove_self(self):
return c.user_is_loggedin and c.site.is_moderator(c.user)
@property
def has_invite(self):
return c.user_is_loggedin and c.site.is_moderator_invite(c.user)
def moderator_editable(self, user, row_type):
if not c.user_is_loggedin:
return False
elif c.user_is_admin:
return True
elif row_type == self.type:
return c.user != user and c.site.can_demod(c.user, user)
elif row_type == self.invite_type:
return c.site.is_unlimited_moderator(c.user)
else:
return False
def user_row(self, row_type, user, editable=True):
perms = ModeratorPermissions(
user, row_type, self.perms_by_type[row_type].get(user._id),
editable=editable)
return UserTableItem(user, row_type, self.cells, self.container_name,
editable, self.remove_action, rel=perms)
@property
def user_rows(self):
return self._user_rows(
self.type, self.user_ids(),
lambda u: self.moderator_editable(u, self.type))
@property
def invited_user_rows(self):
return self._user_rows(
self.invite_type, self.invited_user_ids(),
lambda u: self.moderator_editable(u, self.invite_type))
def _sort_user_ids(self, row_type):
for user_id, perms in self.perms_by_type[row_type].iteritems():
if perms is None:
yield user_id
for user_id, perms in self.perms_by_type[row_type].iteritems():
if perms is not None:
yield user_id
def user_ids(self):
return list(self._sort_user_ids(self.type))
def invited_user_ids(self):
return list(self._sort_user_ids(self.invite_type))
class BannedList(UserList):
"""List of users banned from a given reddit"""
type = 'banned'
def __init__(self, *k, **kw):
UserList.__init__(self, *k, **kw)
rels = getattr(c.site, 'each_%s' % self.type)
self.rels = OrderedDict((rel._thing2_id, rel) for rel in rels(data=True))
self.cells += ('note',)
def user_row(self, row_type, user, editable=True):
rel = self.rels.get(user._id, None)
return UserTableItem(user, row_type, self.cells, self.container_name,
editable, self.remove_action, rel)
@property
def form_title(self):
return _('ban users')
@property
def table_title(self):
return _('banned users')
def user_ids(self):
return self.rels.keys()
class WikiBannedList(BannedList):
"""List of users banned from editing a given wiki"""
type = 'wikibanned'
class WikiMayContributeList(UserList):
"""List of users allowed to contribute to a given wiki"""
type = 'wikicontributor'
@property
def form_title(self):
return _('add a wiki contributor')
@property
def table_title(self):
return _('wiki page contributors')
def user_ids(self):
return c.site.wikicontributor
class DetailsPage(LinkInfoPage):
extension_handling= False
@@ -4337,6 +4214,9 @@ class ModeratorPermissions(Templated):
Templated.__init__(self, permissions_type=permissions_type,
editable=editable, embedded=embedded)
def items(self):
return self.permissions.iteritems()
class ListingChooser(Templated):
def __init__(self):
Templated.__init__(self)

View File

@@ -37,6 +37,7 @@ from r2.lib.db.sorts import epoch_seconds
from r2.lib.menus import menu
from r2.lib.menus import NavButton, NamedButton, PageNameNav, NavMenu
from r2.lib.pages.pages import Reddit, TimeSeriesChart, UserList, TabbedPane
from r2.lib.pages import TrafficTableItem
from r2.lib.promote import cost_per_mille, cost_per_click
from r2.lib.template_helpers import format_number
from r2.lib.utils import Storage, to_date, timedelta_by_name
@@ -764,6 +765,9 @@ class TrafficViewerList(UserList):
def container_name(self):
return self.link._fullname
def user_row(self, row_type, user, editable=True):
return TrafficTableItem(user)
class SubredditTrafficReport(Templated):
def __init__(self):

View File

@@ -737,3 +737,12 @@ class UserMessageBuilder(MessageBuilder):
return conversation(self.user, self.parent)
return user_messages(self.user)
class UserListBuilder(QueryBuilder):
def thing_lookup(self, rels):
accounts = Account._byID([rel._thing2_id for rel in rels], data=True)
for rel in rels:
rel._thing2 = accounts.get(rel._thing2_id)
return rels
def wrap_items(self, rels):
return [self.wrap(rel) for rel in rels]

View File

@@ -26,6 +26,7 @@ from vote import *
from report import *
from subreddit import DefaultSR, AllSR, Frontpage
from pylons import i18n, request, g
from pylons.i18n import _
from r2.lib.wrapped import Wrapped
from r2.lib import utils
@@ -107,6 +108,156 @@ class ModActionListing(TableListing): pass
class WikiRevisionListing(TableListing): pass
class UserListing(TableListing):
type = ''
_class = ''
title = ''
form_title = ''
destination = 'friend'
has_add_form = True
headers = None
def __init__(self,
builder,
show_jump_to=False,
show_not_found=False,
jump_to_value=None,
addable=True, **kw):
self.addable = addable
self.show_not_found = show_not_found
self.show_jump_to = show_jump_to
self.jump_to_value = jump_to_value
TableListing.__init__(self, builder, **kw)
@property
def container_name(self):
return c.site._fullname
class FriendListing(UserListing):
type = 'friend'
@property
def _class(self):
return '' if not c.user.gold else 'gold-accent rounded'
@property
def headers(self):
if c.user.gold:
return (_('user'), '', _('note'), _('friendship'), '')
@property
def form_title(self):
return _('add a friend')
@property
def container_name(self):
return c.user._fullname
class EnemyListing(UserListing):
type = 'enemy'
has_add_form = False
@property
def title(self):
return _('blocked users')
@property
def container_name(self):
return c.user._fullname
class BannedListing(UserListing):
type = 'banned'
@property
def form_title(self):
return _("ban users")
@property
def title(self):
return _("users banned from"
" /r/%(subreddit)s") % dict(subreddit=c.site.name)
class WikiBannedListing(BannedListing):
type = 'wikibanned'
@property
def form_title(self):
return _("ban wiki contibutors")
@property
def title(self):
return _("wiki contibutors banned from"
" /r/%(subreddit)s") % dict(subreddit=c.site.name)
class ContributorListing(UserListing):
type = 'contributor'
@property
def title(self):
return _("approved submitters for"
" /r/%(subreddit)s") % dict(subreddit=c.site.name)
@property
def form_title(self):
return _("add approved submitter")
class WikiMayContributeListing(ContributorListing):
type = 'wikicontributor'
@property
def title(self):
return _("approved wiki contributors"
" for /r/%(subreddit)s") % dict(subreddit=c.site.name)
@property
def form_title(self):
return _("add approved wiki contributor")
class InvitedModListing(UserListing):
type = 'moderator_invite'
form_title = _('invite moderator')
remove_self_title = _('you are a moderator of this subreddit. %(action)s')
def sort_moderators(self, items):
items = [(item, item.rel.get_permissions()) for item in items]
for item, permissions in items:
if permissions is None or permissions.is_superuser():
yield item
for item, permissions in items:
if permissions is not None and not permissions.is_superuser():
yield item
def get_items(self, **kw):
things, prev, next, bcount, acount = UserListing.get_items(self, **kw)
things = list(self.sort_moderators(things))
return things, prev, next, bcount, acount
@property
def title(self):
return _("invited moderators for"
" %(subreddit)s") % dict(subreddit=c.site.name)
class ModListing(InvitedModListing):
type = 'moderator'
form_title = _('force add moderator')
@property
def has_add_form(self):
return c.user_is_admin
@property
def can_remove_self(self):
return c.user_is_loggedin and c.site.is_moderator(c.user)
@property
def has_invite(self):
return c.user_is_loggedin and c.site.is_moderator_invite(c.user)
@property
def title(self):
return _("moderators of /r/%(subreddit)s") % dict(subreddit=c.site.name)
class LinkListing(Listing):
def __init__(self, *a, **kw):
Listing.__init__(self, *a, **kw)

View File

@@ -8231,6 +8231,16 @@ body.with-listing-chooser {
}
}
.user-jumped-to {
border-radius: 5px;
-moz-border-radius: 5px;
border: 1px solid #DDF;
display: inline-block;
margin-top: 10px;
padding: 10px 15px;
background-color: #EEF;
}
.submit_text {
display: none;
max-height: 250px;

View File

@@ -21,12 +21,12 @@
###############################################################################
<%namespace file="printablebuttons.html" import="ynbutton" />
<%namespace file="userlist.html" import="add_form, userlist"/>
<%namespace file="userlisting.html" import="add_form, listing"/>
<%namespace file="utils.html" import="error_field"/>
<div class="${thing._class} usertable">
%if thing.can_remove_self:
${ynbutton(op=thing.remove_action,
${ynbutton(op='unfriend',
title=_("leave"),
executed=_("you are no longer a moderator"),
question=_("stop being a moderator?"),
@@ -40,7 +40,7 @@
%endif
%if thing.has_invite:
${ynbutton(op=thing.invite_action,
${ynbutton(op='accept_moderator_invite',
title=_("accept"),
executed=_("you are now a moderator. welcome to the team!"),
question=_("become a moderator of %s?" % ("/r/" + c.site.name)),
@@ -49,16 +49,10 @@
_class=thing.type + ' accept-invite')}
%endif
%if thing.can_force_add:
${add_form(thing.form_title, thing.destination, thing.type, thing.container_name)}
%endif
%if thing.addable:
<%call expr="add_form(thing.invite_form_title, thing.destination, thing.invite_type, thing.container_name, verb=_('invite'))">
%if thing.addable and thing.has_add_form:
<%call expr="add_form(thing.form_title, thing.destination, thing.type, thing.container_name, verb=_('add'))">
${error_field("ALREADY_MODERATOR", "name")}
</%call>
%endif
%if thing.editable:
${userlist(_("pending invitations"), thing.invite_type, thing.invited_user_rows, thing.table_headers)}
%endif
${userlist(thing.table_title, thing.type, thing.user_rows, thing.table_headers)}
${listing()}
</div>

View File

@@ -22,7 +22,7 @@
<%!
from r2.lib.strings import strings, Score
from r2.lib.pages import WrappedUser, ModList, UserText
from r2.lib.pages import WrappedUser, UserText
%>
<%namespace file="utils.html" import="plain_link, thing_timestamp, text_with_links"/>

View File

@@ -22,7 +22,8 @@
<%!
from r2.lib.strings import strings, Score
from r2.lib.pages import WrappedUser, ModList, SubscribeButton
from r2.lib.pages import WrappedUser, SubscribeButton
from r2.models.listing import ModListing
%>
<%namespace file="utils.html" import="plain_link, thing_timestamp, text_with_links"/>
@@ -41,7 +42,7 @@
%if thing.sr.moderator:
<div class="leavemoderator">
${text_with_links(ModList.remove_self_title % dict(action='(%(action)s)'),
${text_with_links(ModListing.remove_self_title % dict(action='(%(action)s)'),
_sr_path=True,
action=dict(
## TRANSLATORS: this label links to the edit moderators page.

View File

@@ -0,0 +1,138 @@
## The contents of this file are subject to the Common Public Attribution
## License Version 1.0. (the "License"); you may not use this file except in
## compliance with the License. You may obtain a copy of the License at
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
## software over a computer network and provide for limited attribution for the
## Original Developer. In addition, Exhibit A has been modified to be
## consistent with Exhibit B.
##
## Software distributed under the License is distributed on an "AS IS" basis,
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
## the specific language governing rights and limitations under the License.
##
## The Original Code is reddit.
##
## The Original Developer is the Initial Developer. The Initial Developer of
## the Original Code is reddit Inc.
##
## All portions of the code written by reddit are Copyright (c) 2006-2013
## reddit Inc. All Rights Reserved.
###############################################################################
<%!
from r2.models import Sub
%>
<%namespace file="utils.html" import="error_field, plain_link" />
<%def name="add_form(title, dest, add_type, container_name, verb=None)">
<% from r2.models import ModeratorPermissionSet %>
<% from r2.lib.pages import ModeratorPermissions %>
<form action="/post/${dest}"
method="post" class="pretty-form medium-text friend-add"
onsubmit="return post_form(this, '${dest}')"
id="${add_type}">
<h1>${title}</h1>
<input type="hidden" name="action" value="add">
<input type="hidden" name="container" value="${container_name}">
<input type="hidden" name="type" value="${add_type}">
%if add_type in ("banned", "wikibanned"):
<label for="name">${_('who to ban?')} &nbsp;</label>
<input type="text" onfocus="$(this).parent('form').addClass('edited')" class="friend-name" name="name" id="name">
<span class="ban-reason">
<label for="note">${_('why the ban?')}</label>
<input type="text" maxlength="300" name="note" id="note">
<span>${_('(will not be visible to user)')}</span>
</span>
%else:
<input type="text" name="name" id="name">
%endif
%if add_type == "moderator_invite":
${ModeratorPermissions(None, 'moderator_invite',
ModeratorPermissionSet(all=True),
editable=True, embedded=True)}
&#32;
<span class="permissions-edit">
(<a href="javascript:void(0)">${_('change')}</a>)
</span>
%endif
<button class="btn" type="submit">${verb or _("add")}</button>
<span class="status"></span>
${error_field("USER_DOESNT_EXIST", "name")}
%if caller:
${caller.body()}
%endif
</form>
</%def>
<%def name="listing()">
<div class="${thing.type}-table"
style="${'display:none' if not thing.things and not thing.show_not_found else ''}">
<h1>
${thing.title}
</h1>
<table>
%if thing.headers:
<tr>
%for header in thing.headers:
<th>${header}</th>
%endfor
</tr>
%endif
%if thing.things:
%for item in thing.things:
${item}
%endfor
%else:
<tr class="notfound"><td>${_('No items found') if thing.show_not_found else ''}</td></tr>
%endif
</table>
</div>
%if thing.nextprev and (thing.prev or thing.next):
<p class="nextprev"> ${_("view more:")}&#32;
%if thing.prev:
${plain_link(_("first"), thing.first, _sr_path = (c.site != Sub), rel="nofollow first")}
<span class="separator"></span>
${plain_link(unsafe("&lsaquo; " + _("prev")), thing.prev, _sr_path = (c.site != Sub), rel="nofollow prev")}
%endif
%if thing.prev and thing.next:
<span class="separator"></span>
%endif
%if thing.next:
${plain_link(unsafe(_("next") + " &rsaquo;"), thing.next, _sr_path = (c.site != Sub), rel="nofollow next")}
%endif
</p>
%endif
</%def>
<div class="${thing._class} usertable">
%if thing.addable and thing.has_add_form:
${add_form(thing.form_title, thing.destination, thing.type, thing.container_name)}
%endif
%if thing.show_jump_to:
<h1>${_('jump to')}</h1>
<form class="pretty-form medium-text">
<label for="user">${_('username')}&nbsp;</label>
<input type="text" id="user" name="user"
%if thing.jump_to_value:
value="${thing.jump_to_value}"
%endif
>
<button type="submit">${_('go')}</button>
</form>
%endif
${listing()}
%if thing.jump_to_value:
<p class="nextprev">
${plain_link(_("show all"), request.path, rel="nofollow")}
</p>
%endif
</div>

View File

@@ -78,7 +78,7 @@
${timesince(thing.rel._date)}
</span>
%elif thing.name == "permissions":
${thing.rel}
${thing.permissions}
%elif thing.name == "permissionsctl":
%if thing.editable:
<span class="permissions-edit">