{"action":"created","comment":{"url":"https://api.github.com/repos/edx/ecommerce/pulls/comments/36414108","id":36414108,"diff_hunk":"@@ -67,25 +77,182 @@ define([\n             },\n \n             relations: [{\n+                collectionType: ProductCollection,\n                 type: Backbone.HasMany,\n                 key: 'products',\n-                relatedModel: CourseSeatModel,\n+                relatedModel: CourseSeat,\n                 includeInJSON: false,\n                 parse: true\n             }],\n \n-            getSeats: function () {\n-                // Returns the seat products\n+            /**\n+             * Mapping of course type to an array of course seat types.\n+             */\n+            courseTypeSeatMapping: {\n+                honor: ['honor'],\n+                verified: ['honor', 'verified'],\n+                professional: ['professional'],\n+                credit: ['honor', 'verified', 'credit']\n+            },\n+\n+            initialize: function () {\n+                this.get('products').on('change:id_verification_required', this.triggerIdVerified, this);\n+                this.on('sync', this.removeParentProducts, this);\n+            },\n+\n+            /**\n+             * Alerts listeners that this Course's ID verification status MAY have changed.\n+             */\n+            triggerIdVerified: function (model, value) {\n+                this.trigger('change:id_verification_required', this.isIdVerified());\n+            },\n+\n+            /**\n+             * Removes the parent products from the Product collection.\n+             *\n+             * This product is never exposed to the user, and should be ignored for all data operations.\n+             */\n+            removeParentProducts: function () {\n+                var products = this.get('products'),\n+                    parents = products.filter(function (product) {\n+                        return product.get('structure') === 'parent';\n+                    });\n+\n+                products.remove(parents);\n+            },\n \n-                var seats = this.get('products').filter(function (product) {\n+            /**\n+             * Returns all seats related to this course\n+             *\n+             * @returns {CourseSeat[]}\n+             */\n+            seats: function () {\n+                return this.get('products').filter(function (product) {\n+                    // Filter out parent products since there is no need to display or modify.\n+                    return (product instanceof CourseSeat) && product.get('structure') !== 'parent';\n+                });\n+            },\n+\n+            /**\n+             * Returns an existing CourseSeat corresponding to the given seat type; or creates a new one,\n+             * if one is not found.\n+             *\n+             * @param {String} seatType\n+             * @returns {CourseSeat}\n+             */\n+            getOrCreateSeat: function (seatType) {\n+                var seatClass,\n+                    products = this.get('products'),\n+                    seat = products.find(function (product) {\n                         // Filter out parent products since there is no need to display or modify.\n-                        return (product instanceof CourseSeatModel) && product.get('structure') !== 'parent';\n-                    }),\n+                        return (product instanceof CourseSeat) && (product.seatType === seatType);\n+                    });\n+\n+                if (!seat) {\n+                    seatClass = CourseUtils.getCourseSeatModel(seatType);\n+                    seat = new seatClass();\n+                    products.add(seat);\n+                }\n+\n+                return seat;\n+            },\n+\n+            // TODO Rename to seatMap\n+            /**\n+             * Returns a mapping of certificate types/modes to CourseSeats.\n+             *\n+             * @returns {Object}\n+             */\n+            getSeats: function () {\n+                var seats = this.seats(),\n                     seatTypes = _.map(seats, function (seat) {\n-                        return seat.get('certificate_type');\n+                        return seat.seatType;\n                     });\n \n                 return _.object(seatTypes, seats);\n+            },\n+\n+            /**\n+             * Returns boolean indicating if this Course is verified.\n+             *\n+             * A Course is considered verified if any of its seats requires ID verification.\n+             *\n+             * @returns {boolean}\n+             */\n+            isIdVerified: function () {\n+                return Boolean(_.find(this.getCleanProducts(), function (seat) {\n+                    return seat.get('id_verification_required');\n+                }, this));\n+            },\n+\n+            /**\n+             * Returns an array of seat types relevant to this Course, based on its type.\n+             *\n+             * @returns {String[]} - Array of course seat types, or an empty array if the course type is unrecognized.\n+             */\n+            validSeatTypes: function () {\n+                return _.result(this.courseTypeSeatMapping, this.get('type'), []);\n+            },\n+\n+            /**\n+             * Returns an array of Products relevant to this Course, based on its course type.\n+             *\n+             * This method is primarily intended to clean Products models created to support\n+             * the course form view.\n+             *\n+             * @returns {Product[]}\n+             */\n+            getCleanProducts: function () {\n+                return this.seats().filter(function (seat) {\n+                    return _.contains(this.validSeatTypes(), seat.getSeatType());\n+                }, this);\n+            },\n+\n+            /**\n+             * Save the Course using the publication endpoint.\n+             *\n+             * We use this endpoint because it saves the data, and publishes it\n+             * to external systems, in an atomic fashion. This is desirable to\n+             * avoid synchronization issues across systems.\n+             */\n+            save: function (options) {\n+                var data = {\n+                    id: this.get('id'),\n+                    name: this.get('name'),\n+                    verification_deadline: null\n+                };\n+\n+                // Submit only the relevant products\n+                data.products = _.map(this.getCleanProducts(), function (product) {\n+                    return product.toJSON();\n+                }, this);\n+\n+                if (this.isIdVerified()) {\n+                    var verificationDeadline = this.get('verification_deadline');","path":"ecommerce/static/js/models/course_model.js","position":201,"original_position":201,"commit_id":"01effb7691e1421487e342374bff5718c1a09ca5","original_commit_id":"01effb7691e1421487e342374bff5718c1a09ca5","user":{"login":"clintonb","id":910510,"avatar_url":"https://avatars.githubusercontent.com/u/910510?v=3","gravatar_id":"","url":"https://api.github.com/users/clintonb","html_url":"https://github.com/clintonb","followers_url":"https://api.github.com/users/clintonb/followers","following_url":"https://api.github.com/users/clintonb/following{/other_user}","gists_url":"https://api.github.com/users/clintonb/gists{/gist_id}","starred_url":"https://api.github.com/users/clintonb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clintonb/subscriptions","organizations_url":"https://api.github.com/users/clintonb/orgs","repos_url":"https://api.github.com/users/clintonb/repos","events_url":"https://api.github.com/users/clintonb/events{/privacy}","received_events_url":"https://api.github.com/users/clintonb/received_events","type":"User","site_admin":false},"body":"Updated.","created_at":"2015-08-06T13:35:42Z","updated_at":"2015-08-06T13:35:42Z","html_url":"https://github.com/edx/ecommerce/pull/257#discussion_r36414108","pull_request_url":"https://api.github.com/repos/edx/ecommerce/pulls/257","_links":{"self":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/comments/36414108"},"html":{"href":"https://github.com/edx/ecommerce/pull/257#discussion_r36414108"},"pull_request":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/257"}}},"pull_request":{"url":"https://api.github.com/repos/edx/ecommerce/pulls/257","id":41752566,"html_url":"https://github.com/edx/ecommerce/pull/257","diff_url":"https://github.com/edx/ecommerce/pull/257.diff","patch_url":"https://github.com/edx/ecommerce/pull/257.patch","issue_url":"https://api.github.com/repos/edx/ecommerce/issues/257","number":257,"state":"open","locked":false,"title":"Updated Course/Product saving logic","user":{"login":"clintonb","id":910510,"avatar_url":"https://avatars.githubusercontent.com/u/910510?v=3","gravatar_id":"","url":"https://api.github.com/users/clintonb","html_url":"https://github.com/clintonb","followers_url":"https://api.github.com/users/clintonb/followers","following_url":"https://api.github.com/users/clintonb/following{/other_user}","gists_url":"https://api.github.com/users/clintonb/gists{/gist_id}","starred_url":"https://api.github.com/users/clintonb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clintonb/subscriptions","organizations_url":"https://api.github.com/users/clintonb/orgs","repos_url":"https://api.github.com/users/clintonb/repos","events_url":"https://api.github.com/users/clintonb/events{/privacy}","received_events_url":"https://api.github.com/users/clintonb/received_events","type":"User","site_admin":false},"body":"This logic now resides in the model, instead of the view. Moving the information here maintains a separation of concerns—the view shouldn't care how the model is saved—and clears up issues with the related product models and associated nested views.\r\n\r\nXCOM-520\r\n\r\n@AlasdairSwan @rlucioni @jimabramson @Nickersoft ","created_at":"2015-08-06T05:14:09Z","updated_at":"2015-08-06T13:35:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4947b0f804b2708a7361ab28349539ec913afbb8","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/ecommerce/pulls/257/commits","review_comments_url":"https://api.github.com/repos/edx/ecommerce/pulls/257/comments","review_comment_url":"https://api.github.com/repos/edx/ecommerce/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/ecommerce/issues/257/comments","statuses_url":"https://api.github.com/repos/edx/ecommerce/statuses/01effb7691e1421487e342374bff5718c1a09ca5","head":{"label":"edx:course-form-fix","ref":"course-form-fix","sha":"01effb7691e1421487e342374bff5718c1a09ca5","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":30175039,"name":"ecommerce","full_name":"edx/ecommerce","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/ecommerce","description":"Django application used to manage edX's product catalog and handle orders for those products","fork":false,"url":"https://api.github.com/repos/edx/ecommerce","forks_url":"https://api.github.com/repos/edx/ecommerce/forks","keys_url":"https://api.github.com/repos/edx/ecommerce/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/ecommerce/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/ecommerce/teams","hooks_url":"https://api.github.com/repos/edx/ecommerce/hooks","issue_events_url":"https://api.github.com/repos/edx/ecommerce/issues/events{/number}","events_url":"https://api.github.com/repos/edx/ecommerce/events","assignees_url":"https://api.github.com/repos/edx/ecommerce/assignees{/user}","branches_url":"https://api.github.com/repos/edx/ecommerce/branches{/branch}","tags_url":"https://api.github.com/repos/edx/ecommerce/tags","blobs_url":"https://api.github.com/repos/edx/ecommerce/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/ecommerce/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/ecommerce/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/ecommerce/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/ecommerce/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/ecommerce/languages","stargazers_url":"https://api.github.com/repos/edx/ecommerce/stargazers","contributors_url":"https://api.github.com/repos/edx/ecommerce/contributors","subscribers_url":"https://api.github.com/repos/edx/ecommerce/subscribers","subscription_url":"https://api.github.com/repos/edx/ecommerce/subscription","commits_url":"https://api.github.com/repos/edx/ecommerce/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/ecommerce/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/ecommerce/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/ecommerce/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/ecommerce/contents/{+path}","compare_url":"https://api.github.com/repos/edx/ecommerce/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/ecommerce/merges","archive_url":"https://api.github.com/repos/edx/ecommerce/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/ecommerce/downloads","issues_url":"https://api.github.com/repos/edx/ecommerce/issues{/number}","pulls_url":"https://api.github.com/repos/edx/ecommerce/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/ecommerce/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/ecommerce/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/ecommerce/labels{/name}","releases_url":"https://api.github.com/repos/edx/ecommerce/releases{/id}","created_at":"2015-02-02T06:55:15Z","updated_at":"2015-07-27T18:12:49Z","pushed_at":"2015-08-06T05:14:09Z","git_url":"git://github.com/edx/ecommerce.git","ssh_url":"git@github.com:edx/ecommerce.git","clone_url":"https://github.com/edx/ecommerce.git","svn_url":"https://github.com/edx/ecommerce","homepage":"","size":4622,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":4,"forks":8,"open_issues":4,"watchers":8,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"e7501c33ff144286c01da4964da624ee2405715f","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":30175039,"name":"ecommerce","full_name":"edx/ecommerce","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/ecommerce","description":"Django application used to manage edX's product catalog and handle orders for those products","fork":false,"url":"https://api.github.com/repos/edx/ecommerce","forks_url":"https://api.github.com/repos/edx/ecommerce/forks","keys_url":"https://api.github.com/repos/edx/ecommerce/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/ecommerce/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/ecommerce/teams","hooks_url":"https://api.github.com/repos/edx/ecommerce/hooks","issue_events_url":"https://api.github.com/repos/edx/ecommerce/issues/events{/number}","events_url":"https://api.github.com/repos/edx/ecommerce/events","assignees_url":"https://api.github.com/repos/edx/ecommerce/assignees{/user}","branches_url":"https://api.github.com/repos/edx/ecommerce/branches{/branch}","tags_url":"https://api.github.com/repos/edx/ecommerce/tags","blobs_url":"https://api.github.com/repos/edx/ecommerce/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/ecommerce/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/ecommerce/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/ecommerce/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/ecommerce/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/ecommerce/languages","stargazers_url":"https://api.github.com/repos/edx/ecommerce/stargazers","contributors_url":"https://api.github.com/repos/edx/ecommerce/contributors","subscribers_url":"https://api.github.com/repos/edx/ecommerce/subscribers","subscription_url":"https://api.github.com/repos/edx/ecommerce/subscription","commits_url":"https://api.github.com/repos/edx/ecommerce/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/ecommerce/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/ecommerce/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/ecommerce/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/ecommerce/contents/{+path}","compare_url":"https://api.github.com/repos/edx/ecommerce/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/ecommerce/merges","archive_url":"https://api.github.com/repos/edx/ecommerce/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/ecommerce/downloads","issues_url":"https://api.github.com/repos/edx/ecommerce/issues{/number}","pulls_url":"https://api.github.com/repos/edx/ecommerce/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/ecommerce/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/ecommerce/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/ecommerce/labels{/name}","releases_url":"https://api.github.com/repos/edx/ecommerce/releases{/id}","created_at":"2015-02-02T06:55:15Z","updated_at":"2015-07-27T18:12:49Z","pushed_at":"2015-08-06T05:14:09Z","git_url":"git://github.com/edx/ecommerce.git","ssh_url":"git@github.com:edx/ecommerce.git","clone_url":"https://github.com/edx/ecommerce.git","svn_url":"https://github.com/edx/ecommerce","homepage":"","size":4622,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":4,"forks":8,"open_issues":4,"watchers":8,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/257"},"html":{"href":"https://github.com/edx/ecommerce/pull/257"},"issue":{"href":"https://api.github.com/repos/edx/ecommerce/issues/257"},"comments":{"href":"https://api.github.com/repos/edx/ecommerce/issues/257/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/257/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/ecommerce/pulls/257/commits"},"statuses":{"href":"https://api.github.com/repos/edx/ecommerce/statuses/01effb7691e1421487e342374bff5718c1a09ca5"}}}}