{"action":"created","comment":{"url":"https://api.github.com/repos/oppia/oppia/pulls/comments/37922192","id":37922192,"diff_hunk":"@@ -0,0 +1,872 @@\n+# coding: utf-8\r\n+#\r\n+# Copyright 2015 The Oppia Authors. All Rights Reserved.\r\n+#\r\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\r\n+# you may not use this file except in compliance with the License.\r\n+# You may obtain a copy of the License at\r\n+#\r\n+#      http://www.apache.org/licenses/LICENSE-2.0\r\n+#\r\n+# Unless required by applicable law or agreed to in writing, software\r\n+# distributed under the License is distributed on an \"AS-IS\" BASIS,\r\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n+# See the License for the specific language governing permissions and\r\n+# limitations under the License.\r\n+\r\n+\"\"\"Commands that can be used to operate on collections.\r\n+\r\n+All functions here should be agnostic of how CollectionModel objects are\r\n+stored in the database. In particular, the various query methods should\r\n+delegate to the Collection model class. This will enable the collection\r\n+storage model to be changed without affecting this module and others above it.\r\n+\"\"\"\r\n+\r\n+__author__ = 'Ben Henning'\r\n+\r\n+import copy\r\n+import logging\r\n+import os\r\n+\r\n+from core.domain import collection_domain\r\n+from core.domain import event_services\r\n+from core.domain import exp_services\r\n+from core.domain import rights_manager\r\n+from core.platform import models\r\n+(collection_models,) = models.Registry.import_models([models.NAMES.collection])\r\n+memcache_services = models.Registry.import_memcache_services()\r\n+search_services = models.Registry.import_search_services()\r\n+import feconf\r\n+import utils\r\n+\r\n+\r\n+# This takes additional 'title' and 'category' parameters.\r\n+CMD_CREATE_NEW = 'create_new'\r\n+\r\n+# Name for the collection search index.\r\n+SEARCH_INDEX_COLLECTIONS = 'collections'\r\n+\r\n+\r\n+def _migrate_collection_to_latest_schema(versioned_collection):\r\n+    \"\"\"Holds the responsibility of performing a step-by-step, sequential update\r\n+    of the collection structure based on the schema version of the input\r\n+    collection dictionary. This is very similar to the exploration migration\r\n+    process seen in exp_services. If any of the current collection schemas\r\n+    change, a new conversion function must be added and some code appended to\r\n+    this function to account for that new version.\r\n+\r\n+    Args:\r\n+        versioned_collection: A dict with two keys:\r\n+          - schema_version: the schema version for the collection.\r\n+          - nodes: the list of collection nodes comprising the collection.\r\n+    \"\"\"\r\n+    collection_schema_version = versioned_collection['schema_version']\r\n+    if not (1 <= collection_schema_version\r\n+            <= feconf.CURRENT_COLLECTION_SCHEMA_VERSION):\r\n+        raise Exception(\r\n+            'Sorry, we can only process v1-v%d collection schemas at '\r\n+            'present.' % feconf.CURRENT_COLLECTION_SCHEMA_VERSION)\r\n+\r\n+    # This is where conversion functions will be placed once updates to the\r\n+    # collection schemas happen.\r\n+\r\n+\r\n+# Repository GET methods.\r\n+def _get_collection_memcache_key(collection_id, version=None):\r\n+    \"\"\"Returns a memcache key for an collection.\"\"\"\r\n+    if version:\r\n+        return 'collection-version:%s:%s' % (collection_id, version)\r\n+    else:\r\n+        return 'collection:%s' % collection_id\r\n+\r\n+\r\n+def get_collection_from_model(collection_model, run_conversion=True):\r\n+    \"\"\"Returns a Collection domain object given a collection model loaded\r\n+    from the datastore.\r\n+\r\n+    If run_conversion is True, then the collection's schema version will be\r\n+    checked against the current schema version. If they do not match, the\r\n+    collection will be automatically updated to the latest schema version.\r\n+\r\n+    IMPORTANT NOTE TO DEVELOPERS: In general, run_conversion should never be\r\n+    False. This option is only used for testing that the schema version\r\n+    migration works correctly, and it should never be changed otherwise.\r\n+    \"\"\"\r\n+\r\n+    # Ensure the original collection model does not get altered.\r\n+    versioned_collection = {\r\n+        'schema_version': collection_model.schema_version,\r\n+        'nodes': copy.deepcopy(collection_model.nodes)\r\n+    }\r\n+\r\n+    # Migrate the collection if it is not using the latest schema version.\r\n+    if (run_conversion and collection_model.schema_version !=\r\n+            feconf.CURRENT_COLLECTION_SCHEMA_VERSION):\r\n+        _migrate_collection_to_latest_schema(versioned_collection)\r\n+\r\n+    return collection_domain.Collection(\r\n+        collection_model.id, collection_model.title,\r\n+        collection_model.category, collection_model.objective,\r\n+        versioned_collection['schema_version'], [\r\n+            collection_domain.CollectionNode.from_dict(collection_node_dict)\r\n+            for collection_node_dict in versioned_collection['nodes']\r\n+        ],\r\n+        collection_model.version, collection_model.created_on,\r\n+        collection_model.last_updated)\r\n+\r\n+\r\n+def get_collection_summary_from_model(collection_summary_model):\r\n+    return collection_domain.CollectionSummary(\r\n+        collection_summary_model.id, collection_summary_model.title,\r\n+        collection_summary_model.category, collection_summary_model.objective,\r\n+        collection_summary_model.status,\r\n+        collection_summary_model.community_owned,\r\n+        collection_summary_model.owner_ids,\r\n+        collection_summary_model.editor_ids,\r\n+        collection_summary_model.viewer_ids,\r\n+        collection_summary_model.version,\r\n+        collection_summary_model.collection_model_created_on,\r\n+        collection_summary_model.collection_model_last_updated\r\n+    )\r\n+\r\n+\r\n+def get_collection_by_id(collection_id, strict=True, version=None):\r\n+    \"\"\"Returns a domain object representing a collection.\"\"\"\r\n+    collection_memcache_key = _get_collection_memcache_key(\r\n+        collection_id, version=version)\r\n+    memcached_collection = memcache_services.get_multi(\r\n+        [collection_memcache_key]).get(collection_memcache_key)\r\n+\r\n+    if memcached_collection is not None:\r\n+        return memcached_collection\r\n+    else:\r\n+        collection_model = collection_models.CollectionModel.get(\r\n+            collection_id, strict=strict, version=version)\r\n+        if collection_model:\r\n+            collection = get_collection_from_model(collection_model)\r\n+            memcache_services.set_multi({collection_memcache_key: collection})\r\n+            return collection\r\n+        else:\r\n+            return None\r\n+\r\n+\r\n+def get_collection_summary_by_id(collection_id):\r","path":"core/domain/collection_services.py","position":153,"original_position":153,"commit_id":"71f6c503956f916d74832a7b05a7ee110999477a","original_commit_id":"71f6c503956f916d74832a7b05a7ee110999477a","user":{"login":"seanlip","id":10575562,"avatar_url":"https://avatars.githubusercontent.com/u/10575562?v=3","gravatar_id":"","url":"https://api.github.com/users/seanlip","html_url":"https://github.com/seanlip","followers_url":"https://api.github.com/users/seanlip/followers","following_url":"https://api.github.com/users/seanlip/following{/other_user}","gists_url":"https://api.github.com/users/seanlip/gists{/gist_id}","starred_url":"https://api.github.com/users/seanlip/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanlip/subscriptions","organizations_url":"https://api.github.com/users/seanlip/orgs","repos_url":"https://api.github.com/users/seanlip/repos","events_url":"https://api.github.com/users/seanlip/events{/privacy}","received_events_url":"https://api.github.com/users/seanlip/received_events","type":"User","site_admin":false},"body":"> [@seanlip] Recording offline discussion: let's keep the summaries separate for now, but add a TODO to merge things (and perhaps generate them with a batch job) if there is no divergence by Jan 2016 (say). Also, file an issue for this. We may also want to recharacterize summaries as SearchSnippets or similar, since that's really what they are.\r\n\r\n> > [@BenHenning] I may not remember correctly, but I do not think the offline discussion was particularly picky about keeping the summaries separate. Since the changes in rights_manager introduced the concept of an activity (and related activity types for collections and explorations), I'm inclined to unify the two summaries into an ActivitySummary and differentiate between the two summaries based on their activity type (though this might not even be needed since the summaries are differentiated based on their search indexes, anyway).\r\n\r\nSGTM.","created_at":"2015-08-25T21:29:36Z","updated_at":"2015-08-25T21:29:36Z","html_url":"https://github.com/oppia/oppia/pull/931#discussion_r37922192","pull_request_url":"https://api.github.com/repos/oppia/oppia/pulls/931","_links":{"self":{"href":"https://api.github.com/repos/oppia/oppia/pulls/comments/37922192"},"html":{"href":"https://github.com/oppia/oppia/pull/931#discussion_r37922192"},"pull_request":{"href":"https://api.github.com/repos/oppia/oppia/pulls/931"}}},"pull_request":{"url":"https://api.github.com/repos/oppia/oppia/pulls/931","id":43248622,"html_url":"https://github.com/oppia/oppia/pull/931","diff_url":"https://github.com/oppia/oppia/pull/931.diff","patch_url":"https://github.com/oppia/oppia/pull/931.patch","issue_url":"https://api.github.com/repos/oppia/oppia/issues/931","number":931,"state":"open","locked":false,"title":"Collections backend","user":{"login":"BenHenning","id":12983742,"avatar_url":"https://avatars.githubusercontent.com/u/12983742?v=3","gravatar_id":"","url":"https://api.github.com/users/BenHenning","html_url":"https://github.com/BenHenning","followers_url":"https://api.github.com/users/BenHenning/followers","following_url":"https://api.github.com/users/BenHenning/following{/other_user}","gists_url":"https://api.github.com/users/BenHenning/gists{/gist_id}","starred_url":"https://api.github.com/users/BenHenning/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BenHenning/subscriptions","organizations_url":"https://api.github.com/users/BenHenning/orgs","repos_url":"https://api.github.com/users/BenHenning/repos","events_url":"https://api.github.com/users/BenHenning/events{/privacy}","received_events_url":"https://api.github.com/users/BenHenning/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-08-25T04:27:26Z","updated_at":"2015-08-25T21:29:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":{"login":"seanlip","id":10575562,"avatar_url":"https://avatars.githubusercontent.com/u/10575562?v=3","gravatar_id":"","url":"https://api.github.com/users/seanlip","html_url":"https://github.com/seanlip","followers_url":"https://api.github.com/users/seanlip/followers","following_url":"https://api.github.com/users/seanlip/following{/other_user}","gists_url":"https://api.github.com/users/seanlip/gists{/gist_id}","starred_url":"https://api.github.com/users/seanlip/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanlip/subscriptions","organizations_url":"https://api.github.com/users/seanlip/orgs","repos_url":"https://api.github.com/users/seanlip/repos","events_url":"https://api.github.com/users/seanlip/events{/privacy}","received_events_url":"https://api.github.com/users/seanlip/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/oppia/oppia/pulls/931/commits","review_comments_url":"https://api.github.com/repos/oppia/oppia/pulls/931/comments","review_comment_url":"https://api.github.com/repos/oppia/oppia/pulls/comments{/number}","comments_url":"https://api.github.com/repos/oppia/oppia/issues/931/comments","statuses_url":"https://api.github.com/repos/oppia/oppia/statuses/71f6c503956f916d74832a7b05a7ee110999477a","head":{"label":"oppia:collections-backend","ref":"collections-backend","sha":"71f6c503956f916d74832a7b05a7ee110999477a","user":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"repo":{"id":40687563,"name":"oppia","full_name":"oppia/oppia","owner":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/oppia/oppia","description":"Tool for creating interactive tutors.","fork":false,"url":"https://api.github.com/repos/oppia/oppia","forks_url":"https://api.github.com/repos/oppia/oppia/forks","keys_url":"https://api.github.com/repos/oppia/oppia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/oppia/oppia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/oppia/oppia/teams","hooks_url":"https://api.github.com/repos/oppia/oppia/hooks","issue_events_url":"https://api.github.com/repos/oppia/oppia/issues/events{/number}","events_url":"https://api.github.com/repos/oppia/oppia/events","assignees_url":"https://api.github.com/repos/oppia/oppia/assignees{/user}","branches_url":"https://api.github.com/repos/oppia/oppia/branches{/branch}","tags_url":"https://api.github.com/repos/oppia/oppia/tags","blobs_url":"https://api.github.com/repos/oppia/oppia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/oppia/oppia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/oppia/oppia/git/refs{/sha}","trees_url":"https://api.github.com/repos/oppia/oppia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/oppia/oppia/statuses/{sha}","languages_url":"https://api.github.com/repos/oppia/oppia/languages","stargazers_url":"https://api.github.com/repos/oppia/oppia/stargazers","contributors_url":"https://api.github.com/repos/oppia/oppia/contributors","subscribers_url":"https://api.github.com/repos/oppia/oppia/subscribers","subscription_url":"https://api.github.com/repos/oppia/oppia/subscription","commits_url":"https://api.github.com/repos/oppia/oppia/commits{/sha}","git_commits_url":"https://api.github.com/repos/oppia/oppia/git/commits{/sha}","comments_url":"https://api.github.com/repos/oppia/oppia/comments{/number}","issue_comment_url":"https://api.github.com/repos/oppia/oppia/issues/comments{/number}","contents_url":"https://api.github.com/repos/oppia/oppia/contents/{+path}","compare_url":"https://api.github.com/repos/oppia/oppia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/oppia/oppia/merges","archive_url":"https://api.github.com/repos/oppia/oppia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/oppia/oppia/downloads","issues_url":"https://api.github.com/repos/oppia/oppia/issues{/number}","pulls_url":"https://api.github.com/repos/oppia/oppia/pulls{/number}","milestones_url":"https://api.github.com/repos/oppia/oppia/milestones{/number}","notifications_url":"https://api.github.com/repos/oppia/oppia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/oppia/oppia/labels{/name}","releases_url":"https://api.github.com/repos/oppia/oppia/releases{/id}","created_at":"2015-08-14T00:16:14Z","updated_at":"2015-08-25T11:45:10Z","pushed_at":"2015-08-25T21:00:51Z","git_url":"git://github.com/oppia/oppia.git","ssh_url":"git@github.com:oppia/oppia.git","clone_url":"https://github.com/oppia/oppia.git","svn_url":"https://github.com/oppia/oppia","homepage":"https://www.oppia.org","size":37297,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"open_issues_count":160,"forks":18,"open_issues":160,"watchers":21,"default_branch":"develop"}},"base":{"label":"oppia:develop","ref":"develop","sha":"2464351543eb5147ceec93671a2449251310b17d","user":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"repo":{"id":40687563,"name":"oppia","full_name":"oppia/oppia","owner":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/oppia/oppia","description":"Tool for creating interactive tutors.","fork":false,"url":"https://api.github.com/repos/oppia/oppia","forks_url":"https://api.github.com/repos/oppia/oppia/forks","keys_url":"https://api.github.com/repos/oppia/oppia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/oppia/oppia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/oppia/oppia/teams","hooks_url":"https://api.github.com/repos/oppia/oppia/hooks","issue_events_url":"https://api.github.com/repos/oppia/oppia/issues/events{/number}","events_url":"https://api.github.com/repos/oppia/oppia/events","assignees_url":"https://api.github.com/repos/oppia/oppia/assignees{/user}","branches_url":"https://api.github.com/repos/oppia/oppia/branches{/branch}","tags_url":"https://api.github.com/repos/oppia/oppia/tags","blobs_url":"https://api.github.com/repos/oppia/oppia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/oppia/oppia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/oppia/oppia/git/refs{/sha}","trees_url":"https://api.github.com/repos/oppia/oppia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/oppia/oppia/statuses/{sha}","languages_url":"https://api.github.com/repos/oppia/oppia/languages","stargazers_url":"https://api.github.com/repos/oppia/oppia/stargazers","contributors_url":"https://api.github.com/repos/oppia/oppia/contributors","subscribers_url":"https://api.github.com/repos/oppia/oppia/subscribers","subscription_url":"https://api.github.com/repos/oppia/oppia/subscription","commits_url":"https://api.github.com/repos/oppia/oppia/commits{/sha}","git_commits_url":"https://api.github.com/repos/oppia/oppia/git/commits{/sha}","comments_url":"https://api.github.com/repos/oppia/oppia/comments{/number}","issue_comment_url":"https://api.github.com/repos/oppia/oppia/issues/comments{/number}","contents_url":"https://api.github.com/repos/oppia/oppia/contents/{+path}","compare_url":"https://api.github.com/repos/oppia/oppia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/oppia/oppia/merges","archive_url":"https://api.github.com/repos/oppia/oppia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/oppia/oppia/downloads","issues_url":"https://api.github.com/repos/oppia/oppia/issues{/number}","pulls_url":"https://api.github.com/repos/oppia/oppia/pulls{/number}","milestones_url":"https://api.github.com/repos/oppia/oppia/milestones{/number}","notifications_url":"https://api.github.com/repos/oppia/oppia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/oppia/oppia/labels{/name}","releases_url":"https://api.github.com/repos/oppia/oppia/releases{/id}","created_at":"2015-08-14T00:16:14Z","updated_at":"2015-08-25T11:45:10Z","pushed_at":"2015-08-25T21:00:51Z","git_url":"git://github.com/oppia/oppia.git","ssh_url":"git@github.com:oppia/oppia.git","clone_url":"https://github.com/oppia/oppia.git","svn_url":"https://github.com/oppia/oppia","homepage":"https://www.oppia.org","size":37297,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"open_issues_count":160,"forks":18,"open_issues":160,"watchers":21,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/oppia/oppia/pulls/931"},"html":{"href":"https://github.com/oppia/oppia/pull/931"},"issue":{"href":"https://api.github.com/repos/oppia/oppia/issues/931"},"comments":{"href":"https://api.github.com/repos/oppia/oppia/issues/931/comments"},"review_comments":{"href":"https://api.github.com/repos/oppia/oppia/pulls/931/comments"},"review_comment":{"href":"https://api.github.com/repos/oppia/oppia/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/oppia/oppia/pulls/931/commits"},"statuses":{"href":"https://api.github.com/repos/oppia/oppia/statuses/71f6c503956f916d74832a7b05a7ee110999477a"}}}}