def getSkin(lang="en-US"): """ Function to return the context for the current instance. :param language: ISO language string, optional :type language: None :returns: context dict """ contextCollection = CollectionModel().findOne({'name': 'Context'}) skinFolder = FolderModel().findOne({ 'name': 'Skin', 'parentCollection': 'collection', 'parentId': contextCollection.get('_id') }) if contextCollection else None defaultSkin = { 'name': '', 'colors': { 'primary': '#000000', 'secondary': '#FFFFFF' }, 'about': '' } skin = skinFolder.get( 'meta', defaultSkin) if skinFolder is not None else defaultSkin for s in ['name', 'about']: lookup = jsonld_expander.getByLanguage( skin.get(s, ""), lang if lang and lang not in ["@context.@language", ""] else None) skin[s] = lookup if lookup and lookup not in [ None, [{}], ] else jsonld_expander.getByLanguage(skin[s], None) skin[s] = jsonld_expander.fileObjectToStr(skin[s][0]) if isinstance( skin[s], list) and len(skin[s]) else skin[s] return (skin)
def getSkin(self): skinFolder = Folder().findOne({ 'name': 'Skin', 'parentCollection': 'collection', 'parentId': Collection().findOne({ 'name': 'Context' }).get('_id') }) blankSkin = { 'name': '', 'colors': { 'primary': '#000000', 'secondary': '#FFFFFF' }, 'about': '' } skin = skinFolder.get('meta', blankSkin) ab = getByLanguage(skin.get('about')) dSkin = { "about": ab if isinstance(ab, str) else ab.get('@value', ab.get('@id', '')) if isinstance(ab, dict) else '', "colors": skin.get('colors', blankSkin.get('colors')), "name": getByLanguage(skin.get('name')) } return (dSkin)
def testFileProcessHandler(self): admin, user = self.users # Create a collection, folder, and item collection = Collection().createCollection('collection1', admin, public=True) folder = Folder().createFolder(collection, 'folder1', parentType='collection', public=True) item = Item().createItem('item1', admin, folder) # Upload non-DICOM files self._uploadNonDicomFiles(item, admin) nonDicomItem = Item().load(item['_id'], force=True) self.assertIsNone(nonDicomItem.get('dicom')) # Upload DICOM files self._uploadDicomFiles(item, admin) # Check if the 'dicomItem' is well processed dicomItem = Item().load(item['_id'], force=True) self.assertIn('dicom', dicomItem) self.assertHasKeys(dicomItem['dicom'], ['meta', 'files']) # Check if the files list contain the good keys and all the file are well sorted for i in range(0, 4): self.assertTrue('_id' in dicomItem['dicom']['files'][i]) self.assertTrue('name' in dicomItem['dicom']['files'][i]) self.assertEqual(dicomItem['dicom']['files'][i]['name'], 'dicomFile{}.dcm'.format(i)) self.assertTrue('SeriesNumber' in dicomItem['dicom']['files'][i]['dicom']) self.assertTrue('InstanceNumber' in dicomItem['dicom']['files'][i]['dicom']) self.assertTrue('SliceLocation' in dicomItem['dicom']['files'][i]['dicom']) # Check the common metadata self.assertIsNotNone(dicomItem['dicom']['meta'])
def testDicomWithBinaryValues(self): # One of the test files in the pydicom module will throw an IOError # when parsing metadata. We should work around that and still be able # to import the file samplePath = os.path.join(os.path.dirname(os.path.abspath( pydicom.__file__)), 'data', 'test_files', 'OBXXXX1A.dcm') admin, user = self.users # Create a collection, folder, and item collection = Collection().createCollection('collection5', admin, public=True) folder = Folder().createFolder(collection, 'folder5', parentType='collection', public=True) item = Item().createItem('item5', admin, folder) # Upload this dicom file with open(samplePath, 'rb') as fp, _EventHelper('dicom_viewer.upload.success') as helper: dcmFile = Upload().uploadFromFile( obj=fp, size=os.path.getsize(samplePath), name=os.path.basename(samplePath), parentType='item', parent=item, mimeType='application/dicom', user=user ) self.assertIsNotNone(dcmFile) # Wait for handler success event handled = helper.wait() self.assertTrue(handled) # Check if the 'dicomItem' is well processed dicomItem = Item().load(item['_id'], force=True) self.assertIn('dicom', dicomItem) self.assertHasKeys(dicomItem['dicom'], ['meta', 'files'])
def testSearchForDicomItem(self): admin, user = self.users # Create a collection, folder, and item collection = Collection().createCollection('collection3', admin, public=True) folder = Folder().createFolder(collection, 'folder3', parentType='collection', public=True) item = Item().createItem('item3', admin, folder) # Upload files self._uploadDicomFiles(item, admin) # Search for DICOM item with 'brain research' as common key/value resp = self.request(path='/resource/search', params={ 'q': 'brain research', 'mode': 'dicom', 'types': json.dumps(['item']) }) self.assertStatusOk(resp) self.assertEqual(len(resp.json['item']), 1) self.assertEqual(resp.json['item'][0]['name'], 'item3') # Search for DICOM item with substring 'in resea' as common key/value resp = self.request(path='/resource/search', params={ 'q': 'in resea', 'mode': 'dicom', 'types': json.dumps(['item']) }) self.assertStatusOk(resp) self.assertEqual(len(resp.json['item']), 1) self.assertEqual(resp.json['item'][0]['name'], 'item3')
def testMakeDicomItem(self): admin, user = self.users # create a collection, folder, and item collection = Collection().createCollection('collection2', admin, public=True) folder = Folder().createFolder(collection, 'folder2', parentType='collection', public=True) item = Item().createItem('item2', admin, folder) # Upload files self._uploadDicomFiles(item, admin) # Check the endpoint 'parseDicom' for an admin user dicomItem = Item().load(item['_id'], force=True) dicomItem = self._purgeDicomItem(dicomItem) path = '/item/%s/parseDicom' % dicomItem.get('_id') resp = self.request(path=path, method='POST', user=admin) self.assertStatusOk(resp) dicomItem = Item().load(item['_id'], force=True) self.assertIn('dicom', dicomItem) self.assertHasKeys(dicomItem['dicom'], ['meta', 'files']) # Check the endpoint 'parseDicom' for an non admin user dicomItem = Item().load(item['_id'], force=True) dicomItem = self._purgeDicomItem(dicomItem) path = '/item/%s/parseDicom' % dicomItem.get('_id') resp = self.request(path=path, method='POST', user=user) self.assertStatus(resp, 403)
def setUp(self): base.TestCase.setUp(self) admin = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'Admin', 'lastName': 'Last', 'password': '******', 'admin': True } self.admin = User().createUser(**admin) user = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'First', 'lastName': 'Last', 'password': '******', 'admin': False } self.user = User().createUser(**user) coll = { 'name': 'Test Collection', 'description': 'The description', 'public': True, 'creator': self.admin } self.collection = Collection().createCollection(**coll) Folder().createFolder(parent=self.collection, parentType='collection', name='Public', public=True, creator=self.admin)
def _recalculateSizes(self, progress): fixes = 0 models = [Collection(), User()] steps = sum(model.find().count() for model in models) progress.update(total=steps, current=0) for model in models: for doc in model.find(): progress.update(increment=1) _, f = model.updateSize(doc) fixes += f return fixes
def load(self, info): getPlugin('jobs').load(info) name = 'thumbnails' info['apiRoot'].thumbnail = rest.Thumbnail() for model in (Item(), Collection(), Folder(), User()): model.exposeFields(level=AccessType.READ, fields='_thumbnails') events.bind('model.%s.remove' % model.name, name, removeThumbnails) events.bind('model.file.remove', name, removeThumbnailLink) events.bind('data.process', name, _onUpload)
def lookUpPath(path, user=None, filter=True, force=False): """ Look up a resource in the data hierarchy by path. :param path: path of the resource :param user: user with correct privileges to access path :param filter: Whether the returned model should be filtered. :type filter: bool :param force: if True, don't validate the access. :type force: bool """ path = path.lstrip('/') pathArray = split(path) model = pathArray[0] if model == 'user': username = pathArray[1] parent = User().findOne({'login': username}) if parent is None: raise ResourcePathNotFound('User not found: %s' % username) elif model == 'collection': collectionName = pathArray[1] parent = Collection().findOne({'name': collectionName}) if parent is None: raise ResourcePathNotFound('Collection not found: %s' % collectionName) else: raise ValidationException('Invalid path format') try: document = parent if not force: ModelImporter.model(model).requireAccess(document, user) for token in pathArray[2:]: document, model = lookUpToken(token, model, document) if not force: ModelImporter.model(model).requireAccess(document, user) except (ValidationException, AccessException): # We should not distinguish the response between access and validation errors so that # adversarial users cannot discover the existence of data they don't have access to by # looking up a path. raise ResourcePathNotFound('Path not found: %s' % path) if filter: document = ModelImporter.model(model).filter(document, user) return {'model': model, 'document': document}
def create(self, applet, user): """ Create an Assignment for a given Applet, returning an existing Assignment if one or more exists. :param applet: The ID of the Applet for which to find Assignments. :type applet: str :param user: User :type user: User :returns: New Assignments """ # validate Applet ID try: applet = Applet().load(id=applet, force=True) except: raise ValidationException( message='Invalid Applet ID', field='applet' ) assignmentsCollection = Collection().findOne({'name': 'Assignments'}) # for some reason below didn't work/help.. so I added Assignments manually. if not assignmentsCollection: Collection().createCollection('Assignments') try: assignment = Folder().createFolder( parent=assignmentsCollection, parentType='collection', name=Applet().preferredName(applet), creator=user, reuseExisting=True, public=False) except: assignmentsCollection = Folder().createFolder( parent=user, parentType='user', name='Assignments', creator=user, reuseExisting=True, public=False) assignment = Folder().createFolder( parent=assignmentsCollection, parentType='folder', name=Applet().preferredName(applet), creator=user, reuseExisting=True, public=False) return(assignment)
def __init__(self): super(Collection, self).__init__() self.resourceName = 'collection' self._model = CollectionModel() self.route('DELETE', (':id',), self.deleteCollection) self.route('GET', (), self.find) self.route('GET', (':id',), self.getCollection) self.route('GET', (':id', 'details'), self.getCollectionDetails) self.route('GET', ('details',), self.getCollectionsDetails) self.route('GET', (':id', 'download'), self.downloadCollection) self.route('GET', (':id', 'access'), self.getCollectionAccess) self.route('POST', (), self.createCollection) self.route('PUT', (':id',), self.updateCollection) self.route('PUT', (':id', 'access'), self.updateCollectionAccess) self.route('PUT', (':id', 'metadata'), self.setMetadata) self.route('DELETE', (':id', 'metadata'), self.deleteMetadata)
def _getAssetsFolder(self, folderName): """ Get or create a public folder, in the private "Homepage Assets" collection. This makes the folder effectively "unlisted" as it can't be browsed to by normal users, but its content can still be downloaded directly. :param folderName: The name of the folder to get or create. :return: The new folder document. """ collection = Collection().createCollection(constants.COLLECTION_NAME, public=False, reuseExisting=True) folder = Folder().createFolder(collection, folderName, parentType='collection', public=True, reuseExisting=True) return folder
def findAssignments(self, applet): """ Find all Assignments for a given Applet. :param applet: The ID of the Applet for which to find Assignments. :type applet: str :returns: list of Assignments """ # validate Applet ID try: Applet().load(id=applet, force=True) except: raise ValidationException( message='Invalid Applet ID', field='applet' ) allAssignments = [ { '_id': a['_id'], '_modelType': 'collection' } for a in Collection().find({'name': 'Assignments'}) ] + [ { '_id': a['_id'], '_modelType': 'folder' } for a in Folder().find({'name': 'Assignments'}) ] foundAssignments = Folder().find( { '$and': [ {'$or': [ {'meta.applet.@id': str(applet)}, {'meta.applet.url': str(applet)} ]}, {'$or': [ {'parentId': parent['_id']} for parent in allAssignments ]} ] } ) return(list(foundAssignments))
class Collection(Resource): """API Endpoint for collections.""" def __init__(self): super(Collection, self).__init__() self.resourceName = 'collection' self._model = CollectionModel() self.route('DELETE', (':id',), self.deleteCollection) self.route('GET', (), self.find) self.route('GET', (':id',), self.getCollection) self.route('GET', (':id', 'details'), self.getCollectionDetails) self.route('GET', ('details',), self.getCollectionsDetails) self.route('GET', (':id', 'download'), self.downloadCollection) self.route('GET', (':id', 'access'), self.getCollectionAccess) self.route('POST', (), self.createCollection) self.route('PUT', (':id',), self.updateCollection) self.route('PUT', (':id', 'access'), self.updateCollectionAccess) self.route('PUT', (':id', 'metadata'), self.setMetadata) self.route('DELETE', (':id', 'metadata'), self.deleteMetadata) @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=CollectionModel) @autoDescribeRoute( Description('List or search for collections.') .responseClass('Collection', array=True) .param('name', 'Name of the collection', required=False) .param( 'text', 'Pass this to perform a text search for collections.', required=False ) .pagingParams(defaultSort='name') ) def find(self, name, text, limit, offset, sort): user = self.getCurrentUser() collections = self._model.textSearch( text, user=user, limit=limit, offset=offset ) if text is not None else self._model.list( user=user, offset=offset, limit=limit, sort=sort ) return ([ collection for collection in collections if name==collection[ 'name' ] ]) if name is not None else collections @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=CollectionModel) @autoDescribeRoute( Description('Create a new collection.') .responseClass('Collection') .param('name', 'Name for the collection. Must be unique.') .param('description', 'Collection description.', required=False) .param('public', 'Whether the collection should be publicly visible.', required=False, dataType='boolean', default=False) .errorResponse() .errorResponse('You are not authorized to create collections.', 403) ) def createCollection(self, name, description, public): user = self.getCurrentUser() if not self._model.hasCreatePrivilege(user): raise AccessException('You are not authorized to create collections.') return self._model.createCollection( name=name, description=description, public=public, creator=user) @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=CollectionModel) @autoDescribeRoute( Description('Get a collection by ID.') .responseClass('Collection') .modelParam('id', model=CollectionModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read permission denied on the collection.', 403) ) def getCollection(self, collection): return collection @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Get detailed information of accessible collections.') ) def getCollectionsDetails(self): count = self._model.findWithPermissions(user=self.getCurrentUser()).count() return { 'nCollections': count } @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Get detailed information about a collection.') .modelParam('id', model=CollectionModel, level=AccessType.READ) .errorResponse() .errorResponse('Read access was denied on the collection.', 403) ) def getCollectionDetails(self, collection): return { 'nFolders': self._model.countFolders( collection, user=self.getCurrentUser(), level=AccessType.READ) } @access.public(scope=TokenScope.DATA_READ, cookie=True) @autoDescribeRoute( Description('Download an entire collection as a zip archive.') .modelParam('id', model=CollectionModel, level=AccessType.READ) .jsonParam('mimeFilter', 'JSON list of MIME types to include.', requireArray=True, required=False) .produces('application/zip') .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the collection.', 403) ) def downloadCollection(self, collection, mimeFilter): setResponseHeader('Content-Type', 'application/zip') setContentDisposition(collection['name'] + '.zip') def stream(): zip = ziputil.ZipGenerator(collection['name']) for (path, file) in self._model.fileList( collection, user=self.getCurrentUser(), subpath=False, mimeFilter=mimeFilter): for data in zip.addFile(file, path): yield data yield zip.footer() return stream @access.user(scope=TokenScope.DATA_OWN) @autoDescribeRoute( Description('Get the access control list for a collection.') .modelParam('id', model=CollectionModel, level=AccessType.ADMIN) .errorResponse('ID was invalid.') .errorResponse('Admin permission denied on the collection.', 403) ) def getCollectionAccess(self, collection): return self._model.getFullAccessList(collection) @access.user(scope=TokenScope.DATA_OWN) @filtermodel(model=CollectionModel, addFields={'access'}) @autoDescribeRoute( Description('Set the access control list for a collection.') .modelParam('id', model=CollectionModel, level=AccessType.ADMIN) .jsonParam('access', 'The access control list as JSON.', requireObject=True) .jsonParam('publicFlags', 'List of public access flags to set on the collection.', required=False, requireArray=True) .param('public', 'Whether the collection should be publicly visible.', dataType='boolean', required=False) .param('recurse', 'Whether the policies should be applied to all ' 'folders under this collection as well.', dataType='boolean', default=False, required=False) .param('progress', 'If recurse is set to True, this controls whether ' 'progress notifications will be sent.', dataType='boolean', default=False, required=False) .errorResponse('ID was invalid.') .errorResponse('Admin permission denied on the collection.', 403) ) def updateCollectionAccess(self, collection, access, public, recurse, progress, publicFlags): user = self.getCurrentUser() progress = progress and recurse with ProgressContext(progress, user=user, title='Updating permissions', message='Calculating progress...') as ctx: if progress: ctx.update(total=self._model.subtreeCount( collection, includeItems=False, user=user, level=AccessType.ADMIN)) return self._model.setAccessList( collection, access, save=True, user=user, recurse=recurse, progress=ctx, setPublic=public, publicFlags=publicFlags) @access.user(scope=TokenScope.DATA_READ) @filtermodel(model=CollectionModel) @autoDescribeRoute( Description('Edit a collection by ID.') .responseClass('Collection') .modelParam('id', model=CollectionModel, level=AccessType.WRITE) .param('name', 'Unique name for the collection.', required=False, strip=True) .param('description', 'Collection description.', required=False, strip=True) .errorResponse('ID was invalid.') .errorResponse('Write permission denied on the collection.', 403) ) def updateCollection(self, collection, name, description): if name is not None: collection['name'] = name if description is not None: collection['description'] = description return self._model.updateCollection(collection) @access.user(scope=TokenScope.DATA_OWN) @autoDescribeRoute( Description('Delete a collection by ID.') .modelParam('id', model=CollectionModel, level=AccessType.ADMIN) .errorResponse('ID was invalid.') .errorResponse('Admin permission denied on the collection.', 403) ) def deleteCollection(self, collection): self._model.remove(collection) return {'message': 'Deleted collection %s.' % collection['name']} @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=CollectionModel) @autoDescribeRoute( Description('Set metadata fields on a collection.') .responseClass('Collection') .notes('Set metadata fields to null in order to delete them.') .modelParam('id', model=CollectionModel, level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='body', requireObject=True) .param('allowNull', 'Whether "null" is allowed as a metadata value.', required=False, dataType='boolean', default=False) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the collection.', 403) ) def setMetadata(self, collection, metadata, allowNull): return self._model.setMetadata(collection, metadata, allowNull=allowNull) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(CollectionModel) @autoDescribeRoute( Description('Delete metadata fields on a collection.') .responseClass('Collection') .modelParam('id', model=CollectionModel, level=AccessType.WRITE) .jsonParam( 'fields', 'A JSON list containing the metadata fields to delete', paramType='body', schema={ 'type': 'array', 'items': { 'type': 'string' } } ) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the collection.', 403) ) def deleteMetadata(self, collection, fields): return self._model.deleteMetadata(collection, fields)
def testDownload(self): collection = Collection().createCollection('collection1', public=True) folder = Folder().createFolder(collection, 'folder1', parentType='collection', public=True) item = Item().createItem('item1', self.admin, folder) # Path to test files file1Path = os.path.join(self.filesDir, 'txt1.txt') file2Path = os.path.join(self.filesDir, 'txt2.txt') # Upload files to item with open(file1Path, 'rb') as fp: file1 = Upload().uploadFromFile(fp, os.path.getsize(file1Path), 'txt1.txt', parentType='item', parent=item, user=self.admin) with open(file2Path, 'rb') as fp: file2 = Upload().uploadFromFile(fp, os.path.getsize(file2Path), 'txt2.txt', mimeType='image/jpeg', parentType='item', parent=item, user=self.admin) # Download item and its files several times and ensure downloads are recorded # Each file is downloaded 10 times for _ in range(0, 5): self._downloadItem(item['_id']) self._downloadFile(file1['_id']) self._downloadFile(file2['_id']) # Download each file 1 time by downloading parent folder self._downloadFolder(folder['_id']) # Download each file over 2 requests self._downloadFileInTwoChunks(file1['_id']) self._downloadFileInTwoChunks(file2['_id']) # Download each file partially, adding 1 to start and 4 to requested self._downloadPartialFile(file1['_id']) self._downloadPartialFile(file2['_id']) # Download entire collection # Each file is downloaded 1 additional time path = '/collection/%s/download' % collection['_id'] resp = self.request(path, user=self.admin, isJson=False) # Iterate through generator to trigger download events for data in resp.body: data # Download collection filtered by mime type # file2 is downloaded one additional time path = '/collection/%s/download' % collection['_id'] resp = self.request(path, user=self.admin, isJson=False, method='GET', params={ 'id': collection['_id'], 'mimeFilter': json.dumps(['image/jpeg']) }) # iterate through generator to trigger download events for data in resp.body: data self._checkDownloadsCount(file1['_id'], 14, 18, 13) self._checkDownloadsCount(file2['_id'], 15, 19, 14)