diff --git a/r2/r2/controllers/multi.py b/r2/r2/controllers/multi.py index 72f63d4c9..93f4cbbc4 100644 --- a/r2/r2/controllers/multi.py +++ b/r2/r2/controllers/multi.py @@ -93,23 +93,21 @@ class MultiApiController(RedditController, OAuth2ResourceController): """Fetch a multi's data and subreddit list by name.""" return self._format_multi(multi) - @require_oauth2_scope("subscribe") - @api_doc(api_section.multis, extends=GET_multi) - @validate( - VUser(), - VModhash(), - info=VMultiPath("multipath"), - data=VJSON("model"), - ) - def PUT_multi(self, info, data): - """Create or update a multi.""" - if info['username'].lower() != c.user.name.lower(): + def _write_multi_data(self, path_info, data, fail_if_exists=False): + if path_info['username'].lower() != c.user.name.lower(): raise RedditError('BAD_MULTI_NAME', code=400, fields="multipath") try: - multi = LabeledMulti._byID(info['path']) + multi = LabeledMulti._byID(path_info['path']) except tdb_cassandra.NotFound: - multi = LabeledMulti.create(info['path'], c.user) + multi = None + + if multi: + if fail_if_exists: + raise RedditError('MULTI_EXISTS', code=409, fields="multipath") + else: + multi = LabeledMulti.create(path_info['path'], c.user) + if 'visibility' in data: if data['visibility'] not in ('private', 'public'): @@ -143,6 +141,32 @@ class MultiApiController(RedditController, OAuth2ResourceController): raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409) multi._commit() + return multi + + @require_oauth2_scope("subscribe") + @api_doc(api_section.multis, extends=GET_multi) + @validate( + VUser(), + VModhash(), + path_info=VMultiPath("multipath"), + data=VJSON("model"), + ) + def POST_multi(self, path_info, data): + """Create a multi. Responds with 409 Conflict if it already exists.""" + multi = self._write_multi_data(path_info, data, fail_if_exists=True) + return self._format_multi(multi) + + @require_oauth2_scope("subscribe") + @api_doc(api_section.multis, extends=GET_multi) + @validate( + VUser(), + VModhash(), + path_info=VMultiPath("multipath"), + data=VJSON("model"), + ) + def PUT_multi(self, path_info, data): + """Create or update a multi.""" + multi = self._write_multi_data(path_info, data) return self._format_multi(multi) @require_oauth2_scope("subscribe") diff --git a/r2/r2/lib/errors.py b/r2/r2/lib/errors.py index 14450b390..291b4efe4 100644 --- a/r2/r2/lib/errors.py +++ b/r2/r2/lib/errors.py @@ -124,6 +124,7 @@ error_list = dict(( ('BAD_MULTI_PATH', _('invalid multi path')), ('BAD_MULTI_NAME', _('%(reason)s')), ('MULTI_NOT_FOUND', _('that multireddit doesn\'t exist')), + ('MULTI_EXISTS', _('that multireddit already exists')), ('MULTI_CANNOT_EDIT', _('you can\'t change that multireddit')), ('MULTI_TOO_MANY_SUBREDDITS', _('no more space for subreddits in that multireddit')), ('MULTI_SPECIAL_SUBREDDIT', _("can't add special subreddit %(path)s")), diff --git a/r2/r2/lib/strings.py b/r2/r2/lib/strings.py index 75a10ab54..9e3af2f9e 100644 --- a/r2/r2/lib/strings.py +++ b/r2/r2/lib/strings.py @@ -216,7 +216,6 @@ Note: there are a couple of places outside of your subreddit where someone can c create_multi = _('create a new multi'), awesomeness_goes_here = _('awesomeness goes here'), add_multi_sr = _('add a subreddit to your multi.'), - multi_already_exists = _('that multi already exists'), ) class StringHandler(object): diff --git a/r2/r2/public/static/js/multi.js b/r2/r2/public/static/js/multi.js index aa1532ff8..382663223 100644 --- a/r2/r2/public/static/js/multi.js +++ b/r2/r2/public/static/js/multi.js @@ -56,7 +56,8 @@ r.multi.MultiReddit = Backbone.Model.extend({ return r.utils.joinURLs('/api/multi', this.id) }, - initialize: function() { + initialize: function(attributes, options) { + this.uncreated = options && !!options.isNew this.subreddits = new r.multi.MultiRedditList(this.get('subreddits')) this.subreddits.url = this.url() + '/r/' this.on('change:subreddits', function(model, value) { @@ -77,6 +78,21 @@ r.multi.MultiReddit = Backbone.Model.extend({ return data }, + isNew: function() { + return this.uncreated + }, + + sync: function(method, model, options) { + var res = Backbone.sync.apply(this, arguments) + if (method == 'create') { + res.done(_.bind(function() { + // upon successful creation, unset new flag + this.uncreated = false + }, this)) + } + return res + }, + addSubreddit: function(name, options) { this.subreddits.create({name: name}, options) }, @@ -101,14 +117,6 @@ r.multi.MyMultiCollection = Backbone.Collection.extend({ return model.get('path').toLowerCase() }, - create: function(attributes, options) { - if ('name' in attributes) { - attributes['path'] = this.pathByName(attributes['name']) - delete attributes['name'] - } - Backbone.Collection.prototype.create.call(this, attributes, options) - }, - parse: function(data) { return _.map(data, function(multiData) { return r.multi.multis.reify(multiData) @@ -117,10 +125,6 @@ r.multi.MyMultiCollection = Backbone.Collection.extend({ pathByName: function(name) { return '/user/' + r.config.logged + '/m/' + name - }, - - touchByName: function(name) { - return r.multi.multis.touch(this.pathByName(name)) } }) @@ -306,7 +310,9 @@ r.multi.MultiDetails = Backbone.View.extend({ el: $copyForm, navOnCreate: true, createMulti: _.bind(function(name) { - var newMulti = r.multi.mine.touchByName(name) + var newMulti = new r.multi.MultiReddit({ + path: r.multi.mine.pathByName(name) + }, {isNew: true}) this.model.copyTo(newMulti) return newMulti }, this) @@ -431,30 +437,25 @@ r.multi.MultiCreateForm = Backbone.View.extend({ if (this.options.createMulti) { newMulti = this.options.createMulti(name) } else { - newMulti = r.multi.mine.touchByName(name) + var newMulti = new r.multi.MultiReddit({ + path: r.multi.mine.pathByName(name) + }, {isNew: true}) } - // check if the multi already exists - newMulti.fetch({beforeSend: this.showWorkingDeferred}) - .done(_.bind(function() { - this.showError(r.strings('multi_already_exists')) - }, this)) - .fail(_.bind(function() { - r.multi.mine.create(newMulti, { - wait: true, - beforeSend: this.showWorkingDeferred, - success: _.bind(function(multi) { - this.trigger('create', multi) - if (this.options.navOnCreate) { - window.location = multi.get('path') + '#created' - } - }, this), - error: _.bind(function(multi, xhr) { - var resp = JSON.parse(xhr.responseText) - this.showError(resp.explanation) - }, this) - }) - }, this)) + r.multi.mine.create(newMulti, { + wait: true, + beforeSend: this.showWorkingDeferred, + success: _.bind(function(multi) { + this.trigger('create', multi) + if (this.options.navOnCreate) { + window.location = multi.get('path') + '#created' + } + }, this), + error: _.bind(function(multi, xhr) { + var resp = JSON.parse(xhr.responseText) + this.showError(resp.explanation) + }, this) + }) }, showError: function(error) {