mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
Named Multireddit API updates
Named multireddit objects now show additional fields: * description_md * description_html (read only) * display_name * key_color * icon_url (read only) * weighting_scheme * copied_from (read only, requires owner) The "visibility" field can now also be set to "hidden" via the API. Hidden multireddits will not be shown on a user's sidebar on reddit.com, but will still be visible to API consumers. The "copied_from" field shows the multi's owner which multireddit they copied from. A "weighting_scheme" of "fresh" will favor newer content, rather than forcing there to be at least 1 post from each subreddit. "classic" weighting will use the old format. Note: "fresh" weighting will be enabled in a future commit. "key_color" must be an RGB color of the form #AABBCC. API consumers can choose to set and make use of the key_color field for style purposes. "icon_url" may contain a URL to an icon for this multireddit. API consumers can choose to make use of this icon for style purposes. "display_name" is a human-friendly name for this multireddit. API consumers can choose to make use of this field to set/display friendlier names for this multireddit. Description fields are now included in the base multireddit object, and "description_md" can be updated directly on the multireddit object. The separate description endpoint is still available. All of the above fields can be modified via the existing endpoint, PUT /api/multi/<multipath>, except for fields marked read only. Due to the number of new fields and the absence of an existing PATCH endpoint for /api/multi/<multipath>, the existing PUT endpoint has been updated to NOT clobber fields that aren't included in the multi JSON, and to accept "partial" multireddit objects. This is to prevent fields from getting clobbered by clients that haven't been updated to send all the new data.
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from pylons import c, request, response
|
||||
from pylons import c, g, request, response
|
||||
from pylons.i18n import _
|
||||
|
||||
from r2.config.extensions import set_extension
|
||||
@@ -34,41 +34,51 @@ from r2.models.subreddit import (
|
||||
LabeledMulti,
|
||||
TooManySubredditsError,
|
||||
)
|
||||
from r2.lib.db import tdb_cassandra, thing
|
||||
from r2.lib.wrapped import Wrapped
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib.validator import (
|
||||
validate,
|
||||
VUser,
|
||||
VColor,
|
||||
VLength,
|
||||
VMarkdownLength,
|
||||
VModhash,
|
||||
VMultiPath,
|
||||
VMultiByPath,
|
||||
VOneOf,
|
||||
VSubredditName,
|
||||
VSRByName,
|
||||
VUser,
|
||||
VValidatedJSON,
|
||||
VMarkdownLength,
|
||||
VMultiPath,
|
||||
VMultiByPath,
|
||||
)
|
||||
from r2.lib.pages.things import wrap_things
|
||||
from r2.lib.jsontemplates import (
|
||||
LabeledMultiJsonTemplate,
|
||||
LabeledMultiDescriptionJsonTemplate,
|
||||
)
|
||||
from r2.lib.errors import errors, RedditError
|
||||
from r2.lib.errors import RedditError
|
||||
|
||||
|
||||
multi_sr_data_json_spec = VValidatedJSON.Object({
|
||||
'name': VSubredditName('name', allow_language_srs=True),
|
||||
})
|
||||
|
||||
MAX_DESC = 10000
|
||||
MAX_DISP_NAME = 50
|
||||
WRITABLE_MULTI_FIELDS = ('visibility', 'description_md', 'display_name',
|
||||
'key_color', 'weighting_scheme')
|
||||
|
||||
multi_json_spec = VValidatedJSON.Object({
|
||||
'visibility': VOneOf('visibility', ('private', 'public')),
|
||||
multi_json_spec = VValidatedJSON.PartialObject({
|
||||
'description_md': VMarkdownLength('description_md', max_length=MAX_DESC,
|
||||
empty_error=None),
|
||||
'display_name': VLength('display_name', max_length=MAX_DISP_NAME),
|
||||
'key_color': VColor('key_color'),
|
||||
'visibility': VOneOf('visibility', ('private', 'public', 'hidden')),
|
||||
'weighting_scheme': VOneOf('weighting_scheme', ('classic', 'fresh')),
|
||||
'subreddits': VValidatedJSON.ArrayOf(multi_sr_data_json_spec),
|
||||
})
|
||||
|
||||
|
||||
multi_description_json_spec = VValidatedJSON.Object({
|
||||
'body_md': VMarkdownLength('body_md', max_length=10000, empty_error=None),
|
||||
'body_md': VMarkdownLength('body_md', max_length=MAX_DESC, empty_error=None),
|
||||
})
|
||||
|
||||
|
||||
@@ -138,14 +148,25 @@ class MultiApiController(RedditController):
|
||||
return sr_props
|
||||
|
||||
def _write_multi_data(self, multi, data):
|
||||
multi.visibility = data['visibility']
|
||||
srs = data.pop('subreddits', None)
|
||||
if srs is not None:
|
||||
multi.clear_srs()
|
||||
try:
|
||||
self._add_multi_srs(multi, srs)
|
||||
except:
|
||||
multi._revert()
|
||||
raise
|
||||
|
||||
multi.clear_srs()
|
||||
try:
|
||||
self._add_multi_srs(multi, data['subreddits'])
|
||||
except:
|
||||
multi._revert()
|
||||
raise
|
||||
if 'icon_name' in data:
|
||||
try:
|
||||
multi.set_icon_by_name(data.pop('icon_name'))
|
||||
except:
|
||||
multi._revert()
|
||||
raise
|
||||
|
||||
for key, val in data.iteritems():
|
||||
if key in WRITABLE_MULTI_FIELDS:
|
||||
setattr(multi, key, val)
|
||||
|
||||
multi._commit()
|
||||
return multi
|
||||
|
||||
@@ -283,31 +283,6 @@ class SubredditJsonTemplate(ThingJsonTemplate):
|
||||
else:
|
||||
return ThingJsonTemplate.thing_attr(self, thing, attr)
|
||||
|
||||
class LabeledMultiJsonTemplate(ThingJsonTemplate):
|
||||
_data_attrs_ = ThingJsonTemplate.data_attrs(
|
||||
can_edit="can_edit",
|
||||
name="name",
|
||||
path="path",
|
||||
subreddits="srs",
|
||||
visibility="visibility",
|
||||
)
|
||||
del _data_attrs_["id"]
|
||||
|
||||
def kind(self, wrapped):
|
||||
return "LabeledMulti"
|
||||
|
||||
@classmethod
|
||||
def sr_props(cls, thing, srs):
|
||||
sr_props = thing.sr_props
|
||||
return [dict(sr_props[sr._id], name=sr.name) for sr in srs]
|
||||
|
||||
def thing_attr(self, thing, attr):
|
||||
if attr == "srs":
|
||||
return self.sr_props(thing, thing.srs)
|
||||
elif attr == "can_edit":
|
||||
return c.user_is_loggedin and thing.can_edit(c.user)
|
||||
else:
|
||||
return ThingJsonTemplate.thing_attr(self, thing, attr)
|
||||
|
||||
class LabeledMultiDescriptionJsonTemplate(ThingJsonTemplate):
|
||||
_data_attrs_ = dict(
|
||||
@@ -326,6 +301,49 @@ class LabeledMultiDescriptionJsonTemplate(ThingJsonTemplate):
|
||||
else:
|
||||
return ThingJsonTemplate.thing_attr(self, thing, attr)
|
||||
|
||||
|
||||
class LabeledMultiJsonTemplate(LabeledMultiDescriptionJsonTemplate):
|
||||
_data_attrs_ = ThingJsonTemplate.data_attrs(
|
||||
can_edit="can_edit",
|
||||
copied_from="copied_from",
|
||||
description_html="description_html",
|
||||
description_md="description_md",
|
||||
display_name="display_name",
|
||||
key_color="key_color",
|
||||
icon_url="icon_url",
|
||||
name="name",
|
||||
path="path",
|
||||
subreddits="srs",
|
||||
visibility="visibility",
|
||||
weighting_scheme="weighting_scheme",
|
||||
)
|
||||
del _data_attrs_["id"]
|
||||
|
||||
def kind(self, wrapped):
|
||||
return "LabeledMulti"
|
||||
|
||||
@classmethod
|
||||
def sr_props(cls, thing, srs):
|
||||
sr_props = thing.sr_props
|
||||
return [dict(sr_props[sr._id], name=sr.name) for sr in srs]
|
||||
|
||||
def thing_attr(self, thing, attr):
|
||||
if attr == "srs":
|
||||
return self.sr_props(thing, thing.srs)
|
||||
elif attr == "can_edit":
|
||||
return c.user_is_loggedin and thing.can_edit(c.user)
|
||||
elif attr == "copied_from":
|
||||
if thing.can_edit(c.user):
|
||||
return thing.copied_from
|
||||
else:
|
||||
return None
|
||||
elif attr == "display_name":
|
||||
return thing.display_name or thing.name
|
||||
else:
|
||||
super_ = super(LabeledMultiJsonTemplate, self)
|
||||
return super_.thing_attr(thing, attr)
|
||||
|
||||
|
||||
class IdentityJsonTemplate(ThingJsonTemplate):
|
||||
_data_attrs_ = ThingJsonTemplate.data_attrs(
|
||||
comment_karma="comment_karma",
|
||||
|
||||
@@ -1964,11 +1964,18 @@ class ProfilePage(Reddit):
|
||||
|
||||
rb.push(scb)
|
||||
|
||||
multis = [m for m in LabeledMulti.by_owner(self.user)
|
||||
if m.visibility == "public"]
|
||||
if multis:
|
||||
public_multis = [m for m in LabeledMulti.by_owner(self.user)
|
||||
if m.is_public()]
|
||||
if public_multis:
|
||||
scb = SideContentBox(title=_("public multireddits"), content=[
|
||||
SidebarMultiList(multis)
|
||||
SidebarMultiList(public_multis)
|
||||
])
|
||||
rb.push(scb)
|
||||
|
||||
hidden_multis = [m for m in multis if m.is_hidden()]
|
||||
if c.user == self.user and hidden_multis:
|
||||
scb = SideContentBox(title=_("hidden multireddits"), content=[
|
||||
SidebarMultiList(hidden_multis)
|
||||
])
|
||||
rb.push(scb)
|
||||
|
||||
@@ -4742,7 +4749,8 @@ class ListingChooser(Templated):
|
||||
multis = LabeledMulti.by_owner(c.user)
|
||||
multis.sort(key=lambda multi: multi.name.lower())
|
||||
for multi in multis:
|
||||
self.add_item("multi", multi.name, site=multi)
|
||||
if not multi.is_hidden():
|
||||
self.add_item("multi", multi.name, site=multi)
|
||||
|
||||
explore_sr = g.live_config["listing_chooser_explore_sr"]
|
||||
if explore_sr:
|
||||
|
||||
@@ -1737,6 +1737,21 @@ class VColor(Validator):
|
||||
}
|
||||
|
||||
|
||||
class VColor(Validator):
|
||||
"""Validate a string as being a 6 digit hex color starting with #"""
|
||||
color_re = re.compile(r"\A#[a-zA-Z0-9]{6}\Z")
|
||||
|
||||
def run(self, color):
|
||||
if color and self.color_re.match(color):
|
||||
return color
|
||||
else:
|
||||
return None
|
||||
|
||||
def param_docs(self):
|
||||
return {
|
||||
self.param: "A 6-digit rgb hex color, e.g. `#AABBCC`"
|
||||
}
|
||||
|
||||
class VMenu(Validator):
|
||||
|
||||
def __init__(self, param, menu_cls, remember = True, **kw):
|
||||
|
||||
@@ -279,6 +279,21 @@ class Subreddit(Thing, Printable, BaseSite):
|
||||
'private',
|
||||
}
|
||||
|
||||
KEY_COLORS = {
|
||||
'': N_('default'),
|
||||
'#ff4500': N_('orangered'),
|
||||
'#ffd635': N_('yellow'),
|
||||
'#fff03e': N_('highlight'),
|
||||
'#7cd344': N_('green'),
|
||||
'#25b79f': N_('teal'),
|
||||
'#24a0ed': N_('blue'),
|
||||
'#ea0027': N_('red'),
|
||||
'#ff8717': N_('orange'),
|
||||
'#c7e223': N_('lime'),
|
||||
'#46a508': N_('dark green'),
|
||||
'#008985': N_('dark teal'),
|
||||
'#0079d3': N_('alien blue'),
|
||||
}
|
||||
# note: for purposely unrenderable reddits (like promos) set author_id = -1
|
||||
@classmethod
|
||||
def _new(cls, name, title, author_id, ip, lang = g.lang, type = 'public',
|
||||
@@ -1554,7 +1569,12 @@ class LabeledMulti(tdb_cassandra.Thing, MultiReddit):
|
||||
_defaults = dict(MultiReddit._defaults,
|
||||
visibility='private',
|
||||
description_md='',
|
||||
copied_from=None, # for internal analysis/bookkeeping purposes
|
||||
display_name='',
|
||||
copied_from=None,
|
||||
key_color=None,
|
||||
icon_id=None,
|
||||
icon_url=None,
|
||||
weighting_scheme="classic",
|
||||
)
|
||||
_extra_schema_creation_args = {
|
||||
"key_validation_class": tdb_cassandra.UTF8_TYPE,
|
||||
@@ -1677,7 +1697,7 @@ class LabeledMulti(tdb_cassandra.Thing, MultiReddit):
|
||||
|
||||
@property
|
||||
def allows_referrers(self):
|
||||
if self.visibility != 'public':
|
||||
if not self.is_public():
|
||||
return False
|
||||
return super(LabeledMulti, self).allows_referrers
|
||||
|
||||
@@ -1687,11 +1707,17 @@ class LabeledMulti(tdb_cassandra.Thing, MultiReddit):
|
||||
return _('%s subreddits curated by /u/%s') % (self.name, self.owner.name)
|
||||
return _('%s subreddits') % self.name
|
||||
|
||||
def is_public(self):
|
||||
return self.visibility == "public"
|
||||
|
||||
def is_hidden(self):
|
||||
return self.visibility == "hidden"
|
||||
|
||||
def can_view(self, user):
|
||||
if c.user_is_admin:
|
||||
return True
|
||||
|
||||
return user == self.owner or self.visibility == 'public'
|
||||
return user == self.owner or self.is_public()
|
||||
|
||||
def can_edit(self, user):
|
||||
if c.user_is_admin and self.owner == Account.system_user():
|
||||
|
||||
Reference in New Issue
Block a user