class MultiApiController(RedditController): on_validation_error = staticmethod(abort_with_error) def pre(self): set_extension(request.environ, "json") RedditController.pre(self) @require_oauth2_scope("read") @validate(VUser()) @api_doc(api_section.multis, uri="/api/multi/mine") def GET_my_multis(self): """Fetch a list of multis belonging to the current user.""" multis = LabeledMulti.by_owner(c.user) wrapped = wrap_things(*multis) resp = [w.render() for w in wrapped] return self.api_wrapper(resp) def _format_multi(self, multi): resp = wrap_things(multi)[0].render() return self.api_wrapper(resp) @require_oauth2_scope("read") @validate(multi=VMultiByPath("multipath", require_view=True)) @api_doc( api_section.multis, uri="/api/multi/{multipath}", uri_variants=['/api/filter/{filterpath}'], ) def GET_multi(self, multi): """Fetch a multi's data and subreddit list by name.""" return self._format_multi(multi) def _check_new_multi_path(self, path_info): if path_info['username'].lower() != c.user.name.lower(): raise RedditError('MULTI_CANNOT_EDIT', code=403, fields='multipath') def _add_multi_srs(self, multi, sr_datas): srs = Subreddit._by_name(sr_data['name'] for sr_data in sr_datas) for sr in srs.itervalues(): if isinstance(sr, FakeSubreddit): raise RedditError('MULTI_SPECIAL_SUBREDDIT', msg_params={'path': sr.path}, code=400) sr_props = {} for sr_data in sr_datas: try: sr = srs[sr_data['name']] except KeyError: raise RedditError('SUBREDDIT_NOEXIST', code=400) else: # name is passed in via the API data format, but should not be # stored on the model. del sr_data['name'] sr_props[sr] = sr_data try: multi.add_srs(sr_props) except TooManySubredditsError as e: raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409) return sr_props def _write_multi_data(self, multi, data): multi.visibility = data['visibility'] multi.clear_srs() try: self._add_multi_srs(multi, data['subreddits']) except: multi._revert() raise multi._commit() return multi @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), path_info=VMultiPath("multipath"), data=VValidatedJSON("model", multi_json_spec), ) @api_doc(api_section.multis, extends=GET_multi) def POST_multi(self, path_info, data): """Create a multi. Responds with 409 Conflict if it already exists.""" self._check_new_multi_path(path_info) try: LabeledMulti._byID(path_info['path']) except tdb_cassandra.NotFound: multi = LabeledMulti.create(path_info['path'], c.user) response.status = 201 else: raise RedditError('MULTI_EXISTS', code=409, fields='multipath') self._write_multi_data(multi, data) return self._format_multi(multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), path_info=VMultiPath("multipath"), data=VValidatedJSON("model", multi_json_spec), ) @api_doc(api_section.multis, extends=GET_multi) def PUT_multi(self, path_info, data): """Create or update a multi.""" self._check_new_multi_path(path_info) try: multi = LabeledMulti._byID(path_info['path']) except tdb_cassandra.NotFound: multi = LabeledMulti.create(path_info['path'], c.user) response.status = 201 self._write_multi_data(multi, data) return self._format_multi(multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), ) @api_doc(api_section.multis, extends=GET_multi) def DELETE_multi(self, multi): """Delete a multi.""" multi.delete() def _copy_multi(self, from_multi, to_path_info): self._check_new_multi_path(to_path_info) to_owner = Account._by_name(to_path_info['username']) try: LabeledMulti._byID(to_path_info['path']) except tdb_cassandra.NotFound: to_multi = LabeledMulti.copy(to_path_info['path'], from_multi, owner=to_owner) else: raise RedditError('MULTI_EXISTS', code=409, fields='multipath') return to_multi @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), from_multi=VMultiByPath("from", require_view=True, kinds='m'), to_path_info=VMultiPath( "to", docs={"to": "destination multireddit url path"}, ), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/copy", ) def POST_multi_copy(self, from_multi, to_path_info): """Copy a multi. Responds with 409 Conflict if the target already exists. A "copied from ..." line will automatically be appended to the description. """ to_multi = self._copy_multi(from_multi, to_path_info) from_path = from_multi.path to_multi.copied_from = from_path if to_multi.description_md: to_multi.description_md += '\n\n' to_multi.description_md += _('copied from %(source)s') % { # force markdown linking since /user/foo is not autolinked 'source': '[%s](%s)' % (from_path, from_path) } to_multi.visibility = 'private' to_multi._commit() return self._format_multi(to_multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), from_multi=VMultiByPath("from", require_edit=True, kinds='m'), to_path_info=VMultiPath( "to", docs={"to": "destination multireddit url path"}, ), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/rename", ) def POST_multi_rename(self, from_multi, to_path_info): """Rename a multi.""" to_multi = self._copy_multi(from_multi, to_path_info) from_multi.delete() return self._format_multi(to_multi) def _get_multi_subreddit(self, multi, sr): resp = LabeledMultiJsonTemplate.sr_props(multi, [sr])[0] return self.api_wrapper(resp) @require_oauth2_scope("read") @validate( VUser(), multi=VMultiByPath("multipath", require_view=True), sr=VSRByName('srname'), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/r/{srname}", uri_variants=['/api/filter/{filterpath}/r/{srname}'], ) def GET_multi_subreddit(self, multi, sr): """Get data about a subreddit in a multi.""" return self._get_multi_subreddit(multi, sr) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), sr_name=VSubredditName('srname', allow_language_srs=True), data=VValidatedJSON("model", multi_sr_data_json_spec), ) @api_doc(api_section.multis, extends=GET_multi_subreddit) def PUT_multi_subreddit(self, multi, sr_name, data): """Add a subreddit to a multi.""" new = not any(sr.name.lower() == sr_name.lower() for sr in multi.srs) data['name'] = sr_name sr_props = self._add_multi_srs(multi, [data]) sr = sr_props.items()[0][0] multi._commit() if new: response.status = 201 return self._get_multi_subreddit(multi, sr) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), sr=VSRByName('srname'), ) @api_doc(api_section.multis, extends=GET_multi_subreddit) def DELETE_multi_subreddit(self, multi, sr): """Remove a subreddit from a multi.""" multi.del_srs(sr) multi._commit() def _format_multi_description(self, multi): resp = LabeledMultiDescriptionJsonTemplate().render(multi).finalize() return self.api_wrapper(resp) @require_oauth2_scope("read") @validate( VUser(), multi=VMultiByPath("multipath", require_view=True, kinds='m'), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/description", ) def GET_multi_description(self, multi): """Get a multi's description.""" return self._format_multi_description(multi) @require_oauth2_scope("read") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True, kinds='m'), data=VValidatedJSON('model', multi_description_json_spec), ) @api_doc(api_section.multis, extends=GET_multi_description) def PUT_multi_description(self, multi, data): """Change a multi's markdown description.""" multi.description_md = data['body_md'] multi._commit() return self._format_multi_description(multi)
class MultiApiController(RedditController): on_validation_error = staticmethod(abort_with_error) def pre(self): set_extension(request.environ, "json") RedditController.pre(self) def _format_multi_list(self, multis, viewer, expand_srs): templ = LabeledMultiJsonTemplate(expand_srs) resp = [ templ.render(multi).finalize() for multi in multis if multi.can_view(viewer) ] return self.api_wrapper(resp) @require_oauth2_scope("read") @validate( user=VAccountByName("username"), expand_srs=VBoolean("expand_srs"), ) @api_doc(api_section.multis, uri="/api/multi/user/{username}") def GET_list_multis(self, user, expand_srs): """Fetch a list of public multis belonging to `username`""" multis = LabeledMulti.by_owner(user) return self._format_multi_list(multis, c.user, expand_srs) @require_oauth2_scope("read") @validate( sr=VSRByName('srname'), expand_srs=VBoolean("expand_srs"), ) def GET_list_sr_multis(self, sr, expand_srs): """Fetch a list of public multis belonging to subreddit `srname`""" multis = LabeledMulti.by_owner(sr) return self._format_multi_list(multis, c.user, expand_srs) @require_oauth2_scope("read") @validate(VUser(), expand_srs=VBoolean("expand_srs")) @api_doc(api_section.multis, uri="/api/multi/mine") def GET_my_multis(self, expand_srs): """Fetch a list of multis belonging to the current user.""" multis = LabeledMulti.by_owner(c.user) return self._format_multi_list(multis, c.user, expand_srs) def _format_multi(self, multi, expand_sr_info=False): multi_info = LabeledMultiJsonTemplate(expand_sr_info).render(multi) return self.api_wrapper(multi_info.finalize()) @require_oauth2_scope("read") @validate( multi=VMultiByPath("multipath", require_view=True), expand_srs=VBoolean("expand_srs"), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}", uri_variants=['/api/filter/{filterpath}'], ) def GET_multi(self, multi, expand_srs): """Fetch a multi's data and subreddit list by name.""" return self._format_multi(multi, expand_srs) def _check_new_multi_path(self, path_info): if path_info['prefix'] == 'r': return self._check_sr_multi_path(path_info) return self._check_user_multi_path(path_info) def _check_user_multi_path(self, path_info): if path_info['owner'].lower() != c.user.name.lower(): raise RedditError('MULTI_CANNOT_EDIT', code=403, fields='multipath') return c.user def _check_sr_multi_path(self, path_info): try: sr = Subreddit._by_name(path_info['owner']) except NotFound: raise RedditError('SUBREDDIT_NOEXIST', code=404) if (not sr.is_moderator_with_perms(c.user, 'config') and not c.user_is_admin): raise RedditError('MULTI_CANNOT_EDIT', code=403, fields='multipath') return sr def _add_multi_srs(self, multi, sr_datas): srs = Subreddit._by_name(sr_data['name'] for sr_data in sr_datas) for sr in srs.itervalues(): if isinstance(sr, FakeSubreddit): raise RedditError('MULTI_SPECIAL_SUBREDDIT', msg_params={'path': sr.path}, code=400) sr_props = {} for sr_data in sr_datas: try: sr = srs[sr_data['name']] except KeyError: raise RedditError('SUBREDDIT_NOEXIST', code=400) else: # name is passed in via the API data format, but should not be # stored on the model. del sr_data['name'] sr_props[sr] = sr_data try: multi.add_srs(sr_props) except TooManySubredditsError as e: raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409) return sr_props def _write_multi_data(self, multi, data): srs = data.pop('subreddits', None) if srs is not None: multi.clear_srs() try: self._add_multi_srs(multi, srs) 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 @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), path_info=VMultiPath("multipath", required=False), data=VValidatedJSON("model", multi_json_spec), ) @api_doc(api_section.multis, extends=GET_multi) def POST_multi(self, path_info, data): """Create a multi. Responds with 409 Conflict if it already exists.""" if not path_info and "path" in data: path_info = VMultiPath("").run(data["path"]) elif 'display_name' in data: # if path not provided, create multi for user path = LabeledMulti.slugify(c.user, data['display_name']) path_info = VMultiPath("").run(path) if not path_info: raise RedditError('BAD_MULTI_PATH', code=400) owner = self._check_new_multi_path(path_info) try: LabeledMulti._byID(path_info['path']) except tdb_cassandra.NotFound: multi = LabeledMulti.create(path_info['path'], owner) response.status = 201 else: raise RedditError('MULTI_EXISTS', code=409, fields='multipath') self._write_multi_data(multi, data) return self._format_multi(multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), path_info=VMultiPath("multipath"), data=VValidatedJSON("model", multi_json_spec), ) @api_doc(api_section.multis, extends=GET_multi) def PUT_multi(self, path_info, data): """Create or update a multi.""" owner = self._check_new_multi_path(path_info) try: multi = LabeledMulti._byID(path_info['path']) except tdb_cassandra.NotFound: multi = LabeledMulti.create(path_info['path'], owner) response.status = 201 self._write_multi_data(multi, data) return self._format_multi(multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), ) @api_doc(api_section.multis, extends=GET_multi) def DELETE_multi(self, multi): """Delete a multi.""" multi.delete() def _copy_multi(self, from_multi, to_path_info, rename=False): """Copy a multi to a user account.""" to_owner = self._check_new_multi_path(to_path_info) # rename requires same owner if rename and from_multi.owner != to_owner: raise RedditError('MULTI_CANNOT_EDIT', code=400) try: LabeledMulti._byID(to_path_info['path']) except tdb_cassandra.NotFound: to_multi = LabeledMulti.copy(to_path_info['path'], from_multi, owner=to_owner) else: raise RedditError('MULTI_EXISTS', code=409, fields='multipath') return to_multi @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), from_multi=VMultiByPath("from", require_view=True, kinds='m'), to_path_info=VMultiPath( "to", required=False, docs={"to": "destination multireddit url path"}, ), display_name=VLength("display_name", max_length=MAX_DISP_NAME, empty_error=None), ) @api_doc( api_section.multis, uri="/api/multi/copy", ) def POST_multi_copy(self, from_multi, to_path_info, display_name): """Copy a multi. Responds with 409 Conflict if the target already exists. A "copied from ..." line will automatically be appended to the description. """ if not to_path_info: if display_name: # if path not provided, copy multi to same owner path = LabeledMulti.slugify(from_multi.owner, display_name) to_path_info = VMultiPath("").run(path) else: raise RedditError('BAD_MULTI_PATH', code=400) to_multi = self._copy_multi(from_multi, to_path_info) from_path = from_multi.path to_multi.copied_from = from_path if to_multi.description_md: to_multi.description_md += '\n\n' to_multi.description_md += _('copied from %(source)s') % { # force markdown linking since /user/foo is not autolinked 'source': '[%s](%s)' % (from_path, from_path) } to_multi.visibility = 'private' if display_name: to_multi.display_name = display_name to_multi._commit() return self._format_multi(to_multi) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), from_multi=VMultiByPath("from", require_edit=True, kinds='m'), to_path_info=VMultiPath( "to", required=False, docs={"to": "destination multireddit url path"}, ), display_name=VLength("display_name", max_length=MAX_DISP_NAME, empty_error=None), ) @api_doc( api_section.multis, uri="/api/multi/rename", ) def POST_multi_rename(self, from_multi, to_path_info, display_name): """Rename a multi.""" if not to_path_info: if display_name: path = LabeledMulti.slugify(from_multi.owner, display_name) to_path_info = VMultiPath("").run(path) else: raise RedditError('BAD_MULTI_PATH', code=400) to_multi = self._copy_multi(from_multi, to_path_info, rename=True) if display_name: to_multi.display_name = display_name to_multi._commit() from_multi.delete() return self._format_multi(to_multi) def _get_multi_subreddit(self, multi, sr): resp = LabeledMultiJsonTemplate.sr_props(multi, [sr])[0] return self.api_wrapper(resp) @require_oauth2_scope("read") @validate( VUser(), multi=VMultiByPath("multipath", require_view=True), sr=VSRByName('srname'), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/r/{srname}", uri_variants=['/api/filter/{filterpath}/r/{srname}'], ) def GET_multi_subreddit(self, multi, sr): """Get data about a subreddit in a multi.""" return self._get_multi_subreddit(multi, sr) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), sr_name=VSubredditName('srname', allow_language_srs=True), data=VValidatedJSON("model", multi_sr_data_json_spec), ) @api_doc(api_section.multis, extends=GET_multi_subreddit) def PUT_multi_subreddit(self, multi, sr_name, data): """Add a subreddit to a multi.""" new = not any(sr.name.lower() == sr_name.lower() for sr in multi.srs) data['name'] = sr_name sr_props = self._add_multi_srs(multi, [data]) sr = sr_props.items()[0][0] multi._commit() if new: response.status = 201 return self._get_multi_subreddit(multi, sr) @require_oauth2_scope("subscribe") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True), sr=VSRByName('srname'), ) @api_doc(api_section.multis, extends=GET_multi_subreddit) def DELETE_multi_subreddit(self, multi, sr): """Remove a subreddit from a multi.""" multi.del_srs(sr) multi._commit() def _format_multi_description(self, multi): resp = LabeledMultiDescriptionJsonTemplate().render(multi).finalize() return self.api_wrapper(resp) @require_oauth2_scope("read") @validate( VUser(), multi=VMultiByPath("multipath", require_view=True, kinds='m'), ) @api_doc( api_section.multis, uri="/api/multi/{multipath}/description", ) def GET_multi_description(self, multi): """Get a multi's description.""" return self._format_multi_description(multi) @require_oauth2_scope("read") @validate( VUser(), VModhash(), multi=VMultiByPath("multipath", require_edit=True, kinds='m'), data=VValidatedJSON('model', multi_description_json_spec), ) @api_doc(api_section.multis, extends=GET_multi_description) def PUT_multi_description(self, multi, data): """Change a multi's markdown description.""" multi.description_md = data['body_md'] multi._commit() return self._format_multi_description(multi)