def test_bundle_crud(self): """ Create, Fetch, Update, and Delete a Bundle """ coll = api.create_collection("Test Collection") args = { "title": "Water 💧 Bundle", "slug": "h2o", "description": "Sploosh", } # Create: bundle = api.create_bundle(coll.uuid, **args) for attr, value in args.items(): self.assertEqual(getattr(bundle, attr), value) self.assertIsInstance(bundle.uuid, UUID) # Fetch: bundle2 = api.get_bundle(bundle.uuid) self.assertEqual(bundle, bundle2) # Update: new_description = "Water Nation Bending Lessons" bundle3 = api.update_bundle(bundle.uuid, description=new_description) self.assertEqual(bundle3.description, new_description) bundle4 = api.get_bundle(bundle.uuid) self.assertEqual(bundle4.description, new_description) # Delete: api.delete_bundle(bundle.uuid) with self.assertRaises(api.BundleNotFound): api.get_bundle(bundle.uuid)
def test_bundle_crud(self): """ Create, Fetch, Update, and Delete a Bundle """ coll = api.create_collection("Test Collection") args = { "title": "Water 💧 Bundle", "slug": "h2o", "description": "Sploosh", } # Create: bundle = api.create_bundle(coll.uuid, **args) for attr, value in args.items(): assert getattr(bundle, attr) == value assert isinstance(bundle.uuid, UUID) # Fetch: bundle2 = api.get_bundle(bundle.uuid) assert bundle == bundle2 # Update: new_description = "Water Nation Bending Lessons" bundle3 = api.update_bundle(bundle.uuid, description=new_description) assert bundle3.description == new_description bundle4 = api.get_bundle(bundle.uuid) assert bundle4.description == new_description # Delete: api.delete_bundle(bundle.uuid) with pytest.raises(api.BundleNotFound): api.get_bundle(bundle.uuid)
def get_bundle_version_number(bundle_uuid, draft_name=None): """ Get the current version number of the specified bundle/draft. If a draft is specified, the update timestamp is used in lieu of a version number. """ cache_key = 'bundle_version:{}:{}'.format(bundle_uuid, draft_name or '') version = cache.get(cache_key) if version is not None: return version else: version = 0 # Default to 0 in case bundle/draft is empty or doesn't exist bundle_metadata = blockstore_api.get_bundle(bundle_uuid) if draft_name: draft_uuid = bundle_metadata.drafts.get(draft_name) # pylint: disable=no-member if draft_uuid: draft_metadata = blockstore_api.get_draft(draft_uuid) # Convert the 'updated_at' datetime info an integer value with microsecond accuracy. updated_at_timestamp = ( draft_metadata.updated_at - datetime(1970, 1, 1, tzinfo=UTC)).total_seconds() version = int(updated_at_timestamp * 1e6) # If we're not using a draft or the draft does not exist [anymore], fall # back to the bundle version, if any versions have been published: if version == 0 and bundle_metadata.latest_version: version = bundle_metadata.latest_version cache.set(cache_key, version, timeout=MAX_BLOCKSTORE_CACHE_DELAY) return version
def create_bundle_link(library_key, link_id, target_opaque_key, version=None): """ Create a new link to the resource with the specified opaque key. For now, only LibraryLocatorV2 opaque keys are supported. """ ref = ContentLibrary.objects.get_by_key(library_key) # Make sure this link ID/name is not already in use: links = blockstore_cache.get_bundle_draft_direct_links_cached( ref.bundle_uuid, DRAFT_NAME) if link_id in links: raise InvalidNameError("That link ID is already in use.") # Determine the target: if not isinstance(target_opaque_key, LibraryLocatorV2): raise TypeError( "For now, only LibraryLocatorV2 opaque keys are supported by create_bundle_link" ) target_bundle_uuid = ContentLibrary.objects.get_by_key( target_opaque_key).bundle_uuid if version is None: version = get_bundle(target_bundle_uuid).latest_version # Create the new link: draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME) set_draft_link(draft.uuid, link_id, target_bundle_uuid, version) # Clear the cache: LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear() CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key)
def get_library(library_key): """ Get the library with the specified key. Does not check permissions. returns a ContentLibraryMetadata instance. Raises ContentLibraryNotFound if the library doesn't exist. """ assert isinstance(library_key, LibraryLocatorV2) ref = ContentLibrary.objects.get_by_key(library_key) bundle_metadata = get_bundle(ref.bundle_uuid) lib_bundle = LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME) num_blocks = len(lib_bundle.get_top_level_usages()) last_published = lib_bundle.get_last_published_time() (has_unpublished_changes, has_unpublished_deletes) = lib_bundle.has_changes() return ContentLibraryMetadata( key=library_key, bundle_uuid=ref.bundle_uuid, title=bundle_metadata.title, description=bundle_metadata.description, num_blocks=num_blocks, version=bundle_metadata.latest_version, last_published=last_published, allow_public_learning=ref.allow_public_learning, allow_public_read=ref.allow_public_read, has_unpublished_changes=has_unpublished_changes, has_unpublished_deletes=has_unpublished_deletes, )
def get_item_definition(cls, item): ref = ContentLibrary.objects.get_by_key(item) lib_bundle = LibraryBundle(item, ref.bundle_uuid, draft_name=DRAFT_NAME) num_blocks = len(lib_bundle.get_top_level_usages()) last_published = lib_bundle.get_last_published_time() last_published_str = None if last_published: last_published_str = last_published.strftime('%Y-%m-%dT%H:%M:%SZ') (has_unpublished_changes, has_unpublished_deletes) = lib_bundle.has_changes() bundle_metadata = get_bundle(ref.bundle_uuid) # NOTE: Increment ContentLibraryIndexer.SCHEMA_VERSION if the following schema is updated to avoid dealing # with outdated indexes which might cause errors due to missing/invalid attributes. return { "schema_version": ContentLibraryIndexer.SCHEMA_VERSION, "id": str(item), "uuid": str(bundle_metadata.uuid), "title": bundle_metadata.title, "description": bundle_metadata.description, "num_blocks": num_blocks, "version": bundle_metadata.latest_version, "last_published": last_published_str, "has_unpublished_changes": has_unpublished_changes, "has_unpublished_deletes": has_unpublished_deletes, # only 'content' field is analyzed by elastisearch, and allows text-search "content": { "id": str(item), "title": bundle_metadata.title, "description": bundle_metadata.description, }, }
def revert_changes(library_key): """ Revert all pending changes to the specified library, restoring it to the last published version. """ ref = ContentLibrary.objects.get_by_key(library_key) bundle = get_bundle(ref.bundle_uuid) if DRAFT_NAME in bundle.drafts: # pylint: disable=unsupported-membership-test draft_uuid = bundle.drafts[DRAFT_NAME] # pylint: disable=unsubscriptable-object delete_draft(draft_uuid) else: return # If there is no draft, no action is needed. LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
def publish_changes(library_key): """ Publish all pending changes to the specified library. """ ref = ContentLibrary.objects.get_by_key(library_key) bundle = get_bundle(ref.bundle_uuid) if DRAFT_NAME in bundle.drafts: # pylint: disable=unsupported-membership-test draft_uuid = bundle.drafts[DRAFT_NAME] # pylint: disable=unsubscriptable-object commit_draft(draft_uuid) else: return # If there is no draft, no action is needed. LibraryBundle(library_key, ref.bundle_uuid).cache.clear() LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear() CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key, update_blocks=True)
def index_libraries(cls, library_keys): """ Index the specified libraries. If they already exist, replace them with new ones. """ searcher = SearchEngine.get_search_engine(cls.INDEX_NAME) library_dicts = [] for library_key in library_keys: ref = ContentLibrary.objects.get_by_key(library_key) lib_bundle = LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME) num_blocks = len(lib_bundle.get_top_level_usages()) last_published = lib_bundle.get_last_published_time() last_published_str = None if last_published: last_published_str = last_published.strftime( '%Y-%m-%dT%H:%M:%SZ') (has_unpublished_changes, has_unpublished_deletes) = lib_bundle.has_changes() bundle_metadata = get_bundle(ref.bundle_uuid) # NOTE: Increment ContentLibraryIndexer.SCHEMA_VERSION if the following schema is updated to avoid dealing # with outdated indexes which might cause errors due to missing/invalid attributes. library_dict = { "schema_version": ContentLibraryIndexer.SCHEMA_VERSION, "id": str(library_key), "uuid": str(bundle_metadata.uuid), "title": bundle_metadata.title, "description": bundle_metadata.description, "num_blocks": num_blocks, "version": bundle_metadata.latest_version, "last_published": last_published_str, "has_unpublished_changes": has_unpublished_changes, "has_unpublished_deletes": has_unpublished_deletes, } library_dicts.append(library_dict) return searcher.index(cls.LIBRARY_DOCUMENT_TYPE, library_dicts)
def get_library(library_key): """ Get the library with the specified key. Does not check permissions. returns a ContentLibraryMetadata instance. Raises ContentLibraryNotFound if the library doesn't exist. """ assert isinstance(library_key, LibraryLocatorV2) ref = ContentLibrary.objects.get_by_key(library_key) bundle_metadata = get_bundle(ref.bundle_uuid) lib_bundle = LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME) (has_unpublished_changes, has_unpublished_deletes) = lib_bundle.has_changes() return ContentLibraryMetadata( key=library_key, bundle_uuid=ref.bundle_uuid, title=bundle_metadata.title, description=bundle_metadata.description, version=bundle_metadata.latest_version, has_unpublished_changes=has_unpublished_changes, has_unpublished_deletes=has_unpublished_deletes, )
def update_bundle_link(library_key, link_id, version=None, delete=False): """ Update a bundle's link to point to the specified version of its target bundle. Use version=None to automatically point to the latest version. Use delete=True to delete the link. """ ref = ContentLibrary.objects.get_by_key(library_key) draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME) if delete: set_draft_link(draft.uuid, link_id, None, None) else: links = blockstore_cache.get_bundle_draft_direct_links_cached(ref.bundle_uuid, DRAFT_NAME) try: link = links[link_id] except KeyError: raise InvalidNameError("That link does not exist.") if version is None: version = get_bundle(link.bundle_uuid).latest_version set_draft_link(draft.uuid, link_id, link.bundle_uuid, version) # Clear the cache: LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
def test_nonexistent_bundle(self): """ Request a bundle that doesn't exist -> BundleNotFound """ with self.assertRaises(api.BundleNotFound): api.get_bundle(BAD_UUID)