From bcc2477f9577dbde399cc8540b047c3550f360f2 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 22 Aug 2024 10:48:48 -0400 Subject: [PATCH 01/14] initial metadata editing functionality --- server/dive_utils/metadata/models.py | 104 ++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 3 deletions(-) diff --git a/server/dive_utils/metadata/models.py b/server/dive_utils/metadata/models.py index 83a1d19..5670f4e 100644 --- a/server/dive_utils/metadata/models.py +++ b/server/dive_utils/metadata/models.py @@ -39,7 +39,7 @@ def initialize(self): ] ) - def createMetadata(self, folder, root, owner, metadata, created_date=None): + def createMetadata(self, folder, root, owner, metadata, created_date=None, unlocked=[]): existing = self.findOne({'DIVEDataset': str(folder['_id'])}) if not existing: if created_date is None: @@ -53,6 +53,7 @@ def createMetadata(self, folder, root, owner, metadata, created_date=None): root=str(root['_id']), metadata=metadata, created=created, + owner=owner, ) else: existing['metadata'] = metadata @@ -60,13 +61,34 @@ def createMetadata(self, folder, root, owner, metadata, created_date=None): existing['root'] = str(root['_id']) existing = self.save(existing) return existing + + def validate(self, doc): if not doc.get('DIVEDataset') or not isinstance(doc['DIVEDataset'], str): raise ValidationException('DIVEDataset must be a string') if 'root' not in doc or not isinstance(doc['root'], str): - raise ValidationException('owner must be a string') + raise ValidationException('root must be a string') return doc + + def updateKey(self, folder, root, owner, key, value, categoricalLimit): + existing = self.findOne({'DIVEDataset': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + query = {'root': str(folder['_id'])} + metadataKeys = DIVE_MetadataKeys().findOne( + query=query, + user=owner, + ) + if not metadataKeys: + raise Exception(f'Could not find the root metadataKeys with folderId: {folder["_id"]}') + if key not in metadataKeys['unlocked']: + raise Exception(f'Key {key} is not unlocked for this metadata and cannot be modified') + existing['metadata'][key] = value + self.save(existing) + # now we need to update the metadataKey + DIVE_MetadataKeys().updateKeyValue(folder, owner, key, value, categoricalLimit) + class DIVE_MetadataKeys(Model): @@ -100,7 +122,7 @@ def initialize(self): ] ) - def createMetadataKeys(self, root, owner, metadataKeys, created_date=None): + def createMetadataKeys(self, root, owner, metadataKeys, created_date=None, unlocked=[]): existing = self.findOne({'root': str(root['_id'])}) if not existing: if created_date is None: @@ -111,6 +133,7 @@ def createMetadataKeys(self, root, owner, metadataKeys, created_date=None): existing = dict( root=str(root['_id']), metadataKeys=metadataKeys, + unlocked=[], created=created, ) else: @@ -122,3 +145,78 @@ def validate(self, doc): if 'root' not in doc or not isinstance(doc['root'], str): raise ValidationException('owner must be a string') return doc + + def modifyKeyPermission(self, folder, owner, key, locked): + existing = self.findOne({'root': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + if owner and self.owner != owner: + raise Exception('Only the Owner can modify key permissions') + elif existing: + if key not in existing['metadataKeys'].keys(): + raise Exception(f'Key: {key} not in the metadata keys to modify permission') + else: + if not existing.get('unlocked', False): + existing['unlocked'] = [] + self.save(existing) + if not locked and key not in existing.get('unlocked', {}): + existing['unlocked'].append(key) + if locked and key in existing['unlocked']: + existing.remove(key) + self.save(existing) + + def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": "categorical"}, locked=True): + # info is {"type": datatype, "set": set(), "count": 0} may include range: {min: number, max: number} + existing = self.findOne({'root': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + if owner and self.owner != owner: + raise Exception('Only the Owner can modify key permissions') + elif existing: + if key in existing['metadataKeys'].keys(): + raise Exception(f'Key: {key} already exists in the dataset and cannot be added') + else: + existing["metadataKeys"][key] = info + if locked and key not in existing.get('unlocked', {}): + existing['unlocked'].append(key) + self.save(existing) + + def deleteKey(self, folder, owner, key): + existing = self.findOne({'root': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + if owner and self.owner != owner: + raise Exception('Only the Owner can modify key permissions') + elif existing: + if key in existing['unlocked']: + existing['unlocked'].remove(key) + if key in existing['metadataKeys'].keys(): + del existing['metadataKeys'][key] + self.save(existing) + else: + raise Exception(f'Key: {key} not found in the current metdata') + + + + def updateKeyValue(self, folder, owner, key, value, categoricalLimit): + existing = self.findOne({'root': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + if key not in existing['metadataKeys'].keys(): + raise Exception(f'Key: {key} is not in the metadata') + keyData = existing['metdataKeys'][key] + category = keyData['category'] + if category == 'categorical': + if len(keyData['set']) + 1 < categoricalLimit: + keyData['set'].add(value) + else: + keyData['category'] = 'search' + del keyData['set'] + if category == 'numerical' and keyData.get('range', False): + range = keyData['range'] + range['min'] = min(value, range['min']) + range['max'] = max(value, range['max']) + keyData['range'] = range + existing['metdataKeys'][key] = keyData + self.save(existing) + From 4aa037d36a3ff9b1352e0af747922df998ea7ed5 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 29 Aug 2024 09:29:51 -0400 Subject: [PATCH 02/14] add endpoints for modification of the metadata keys --- server/dive_server/views_metadata.py | 138 +++++++++++++++++++++++++++ server/dive_utils/metadata/models.py | 12 +-- 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index 76ab390..d1888ad 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -118,6 +118,10 @@ def __init__(self, resourceName): self.route("GET", (':id', 'metadata_keys'), self.get_metadata_keys) self.route("GET", (':id', 'metadata_filter_values'), self.get_metadata_filter) self.route("DELETE", (':rootId',), self.delete_metadata) + self.route("DELETE", (':rootId','delete_key'), self.delete_metadata_key) + self.route("PUT", (':rootId','add_key'), self.add_metadata_key) + self.route("PATCH", (':rootId','modify_key_permission'), self.modify_key_permission) + self.route("PATCH", (':folderId',), self.set_key_value) @access.user @autoDescribeRoute( @@ -565,3 +569,137 @@ def delete_metadata(self, rootId): Folder().save(rootId) else: raise RestException('Could not find a state to delete') + + @access.user + @autoDescribeRoute( + Description("Delete Metadata Key from Metadata Folder").modelParam( + "rootId", + description="Root metadata FolderId", + model=Folder, + level=AccessType.READ, + destName="rootId", + ).param( + "key", + "Metadata key to remove", + required=True, + ) + + ) + def delete_metadata_key(self, rootId, key): + user = self.getCurrentUser() + query = {"root": str(rootId["_id"])} + found = DIVE_Metadata().findOne(query=query, user=user) + if found: + DIVE_MetadataKeys().deleteKey(rootId["_Id"], user, key) + Folder().save(rootId) + else: + raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') + + @autoDescribeRoute( + Description("Add Metadata Key to Metdata Folder").modelParam( + "rootId", + description="Root metadata FolderId", + model=Folder, + level=AccessType.READ, + destName="rootId", + ).param( + "key", + "Metadata key to add", + required=False, + ).param( + "category", + "type of metadata to add", + enum=['numerical', 'categorical', 'search', 'boolean'], + required=True, + default='numerical', + ).param( + "unlocked", + "If this value for each metadata item should be modified by regular users", + dataType='boolean', + required=True, + default=False, + ) + .jsonParam( + "values", + "List of values, either numbers for numerical category or string for categorical, for search this field isn't required. I.E ['key1', 'key2'] or [0, 20]", + required=False, + default=[] + ) + ) + def add_metadata_key(self, rootId, key, category, unlocked, values=[]): + user = self.getCurrentUser() + query = {"root": str(rootId["_id"])} + found = DIVE_Metadata().findOne(query=query, user=user) + if found: + info= { "count": 0, "category": category} + if category == 'categorical': + info['set'] = set(values) + if category == 'numerical': + info['range'] = { 'min': float('inf'), 'max': float('-inf')} + DIVE_MetadataKeys().addKey(rootId["_Id"], user, key, info, unlocked) + Folder().save(rootId) + else: + raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') + + @autoDescribeRoute( + Description("Add Metadata Key to Metdata Folder").modelParam( + "rootId", + description="Root metadata FolderId", + model=Folder, + level=AccessType.READ, + destName="rootId", + ).param( + "key", + "Metadata key to add", + required=False, + ).param( + "unlocked", + "If this value for each metadata item should be modified by regular users", + dataType='boolean', + required=True, + default=False, + ) + + ) + def modify_key_permission(self, rootId, key, unlocked): + user = self.getCurrentUser() + query = {"root": str(rootId["_id"])} + found = DIVE_Metadata().findOne(query=query, user=user) + if found: + DIVE_MetadataKeys().modifyKeyPermission(rootId["_Id"], user, key, unlocked) + Folder().save(rootId) + else: + raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') + + + @autoDescribeRoute( + Description("Set MetadataKey value for a folder").modelParam( + "folder", + description="The folder to set the key on", + model=Folder, + level=AccessType.READ, + destName="rootId", + ).param( + "key", + "Metadata key to add", + required=False, + ).param( + "value", + "Value to set the key to, empty is a None value", + required=True, + default=None, + ) + + ) + def set_key_value(self, folder, key, value): + user = self.getCurrentUser() + query = {"DIVEDataset": str(folder["_id"])} + found = DIVE_Metadata().findOne(query=query, user=user) + if found: + rootId = found['root'] + rootFolder = Folder().load(rootId, user=user) + categoricalLimit = rootFolder['meta'].get(DIVEMetadataFilter, {}).get('categoricalLimit', 50) + DIVE_Metadata().updateKey(folder["id"],rootId["_Id"], user, key, value, categoricalLimit) + Folder().save(rootId) + else: + raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') \ No newline at end of file diff --git a/server/dive_utils/metadata/models.py b/server/dive_utils/metadata/models.py index 5670f4e..238fb64 100644 --- a/server/dive_utils/metadata/models.py +++ b/server/dive_utils/metadata/models.py @@ -71,7 +71,7 @@ def validate(self, doc): raise ValidationException('root must be a string') return doc - def updateKey(self, folder, root, owner, key, value, categoricalLimit): + def updateKey(self, folder, root, owner, key, value, categoricalLimit=50): existing = self.findOne({'DIVEDataset': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') @@ -146,7 +146,7 @@ def validate(self, doc): raise ValidationException('owner must be a string') return doc - def modifyKeyPermission(self, folder, owner, key, locked): + def modifyKeyPermission(self, folder, owner, key, unlocked): existing = self.findOne({'root': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') @@ -159,13 +159,13 @@ def modifyKeyPermission(self, folder, owner, key, locked): if not existing.get('unlocked', False): existing['unlocked'] = [] self.save(existing) - if not locked and key not in existing.get('unlocked', {}): + if unlocked and key not in existing.get('unlocked', {}): existing['unlocked'].append(key) - if locked and key in existing['unlocked']: + if not unlocked and key in existing['unlocked']: existing.remove(key) self.save(existing) - def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": "categorical"}, locked=True): + def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": "categorical"}, unlocked=True): # info is {"type": datatype, "set": set(), "count": 0} may include range: {min: number, max: number} existing = self.findOne({'root': str(folder['_id'])}) if not existing: @@ -177,7 +177,7 @@ def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": raise Exception(f'Key: {key} already exists in the dataset and cannot be added') else: existing["metadataKeys"][key] = info - if locked and key not in existing.get('unlocked', {}): + if unlocked and key not in existing.get('unlocked', {}): existing['unlocked'].append(key) self.save(existing) From efb258fa86b17275188a2f0d4f20a8728d3446af Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 29 Aug 2024 09:42:32 -0400 Subject: [PATCH 03/14] rename params --- server/dive_server/views_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index d1888ad..6ff011c 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -678,7 +678,7 @@ def modify_key_permission(self, rootId, key, unlocked): description="The folder to set the key on", model=Folder, level=AccessType.READ, - destName="rootId", + destName="folder", ).param( "key", "Metadata key to add", From 42448494856fefb4c6a7b43146cf22cadd932591 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Mon, 30 Sep 2024 14:02:17 -0400 Subject: [PATCH 04/14] testing endpoints and configuration --- server/dive_server/crud_dataset.py | 5 +- server/dive_server/views_dataset.py | 10 +- server/dive_server/views_metadata.py | 293 +++++++++++++++++++++------ server/dive_tasks/tasks.py | 5 +- server/dive_utils/metadata/models.py | 55 ++--- server/poetry.lock | 229 +++++++++------------ server/pyproject.toml | 7 +- 7 files changed, 381 insertions(+), 223 deletions(-) diff --git a/server/dive_server/crud_dataset.py b/server/dive_server/crud_dataset.py index ecb071c..ccf77a6 100644 --- a/server/dive_server/crud_dataset.py +++ b/server/dive_server/crud_dataset.py @@ -180,7 +180,10 @@ def get_task_defaults( def get_recursive_datasets( - dsFolder: types.GirderModel, user: types.GirderUserModel, datasetList: List[types.GirderModel], limit: int = -1 + dsFolder: types.GirderModel, + user: types.GirderUserModel, + datasetList: List[types.GirderModel], + limit: int = -1, ): subFolders = list(Folder().childFolders(dsFolder, 'folder', user)) for child in subFolders: diff --git a/server/dive_server/views_dataset.py b/server/dive_server/views_dataset.py index 032b496..73eaba3 100644 --- a/server/dive_server/views_dataset.py +++ b/server/dive_server/views_dataset.py @@ -446,7 +446,9 @@ def get_configuration(self, folder): str(folderParentId), level=AccessType.READ, user=user, force=True ) childFolders = list( - Folder().childFolders(folderParent, folderParentType, sort=[['lowerName', 1]], user=user) + Folder().childFolders( + folderParent, folderParentType, sort=[['lowerName', 1]], user=user + ) ) for index, item in enumerate(childFolders): if item.get('_id') == folder.get('_id'): @@ -463,7 +465,6 @@ def get_configuration(self, folder): if index + 1 < len(childFolders): counter = 1 while index + counter < len(childFolders): - print(childFolders[index + counter].get('meta', {})) if childFolders[index + counter].get('meta', {}).get('annotate', False): next = childFolders[index + counter] break @@ -487,7 +488,6 @@ def get_configuration(self, folder): 'hierarchy': hierarchy, 'metadata': combinedConfiguration, } - print(returnVal) return json.dumps(returnVal, indent=4) @access.user @@ -512,7 +512,8 @@ def get_task_defaults(self, folder): @access.public(scope=TokenScope.DATA_READ, cookie=True) @autoDescribeRoute( - Description("Get a Recursive list of all DIVE Datasets within a parent folder").modelParam( + Description("Get a Recursive list of all DIVE Datasets within a parent folder") + .modelParam( "id", level=AccessType.READ, **DatasetModelParam, @@ -524,7 +525,6 @@ def get_task_defaults(self, folder): default=-1, required=False, ) - ) def get_recursive(self, folder, limit): datasetList = [] diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index 6ff011c..1edec9e 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -114,14 +114,15 @@ def __init__(self, resourceName): ), self.filter_folder, ) + self.route("POST", ("create_metadata_folder", ":id"), self.create_metadata_folder) self.route("POST", (':id', "clone_filter"), self.clone_filter) self.route("GET", (':id', 'metadata_keys'), self.get_metadata_keys) self.route("GET", (':id', 'metadata_filter_values'), self.get_metadata_filter) self.route("DELETE", (':rootId',), self.delete_metadata) - self.route("DELETE", (':rootId','delete_key'), self.delete_metadata_key) - self.route("PUT", (':rootId','add_key'), self.add_metadata_key) - self.route("PATCH", (':rootId','modify_key_permission'), self.modify_key_permission) - self.route("PATCH", (':folderId',), self.set_key_value) + self.route("DELETE", (':rootId', 'delete_key'), self.delete_metadata_key) + self.route("PUT", (':root', 'add_key'), self.add_metadata_key) + self.route("PATCH", (':root', 'modify_key_permission'), self.modify_key_permission) + self.route("PATCH", (':divedataset',), self.set_key_value) @access.user @autoDescribeRoute( @@ -191,7 +192,15 @@ def __init__(self, resourceName): ) ) def process_metadata( - self, folder, sibling_path, fileType, matcher, path_key, displayConfig, ffprobeMetadata, categoricalLimit + self, + folder, + sibling_path, + fileType, + matcher, + path_key, + displayConfig, + ffprobeMetadata, + categoricalLimit, ): # Process the current folder for the specified fileType using the matcher to generate DIVE_Metadata # make sure the folder is set to a DIVE Metadata folder using DIVE_METADATA = True @@ -263,12 +272,22 @@ def process_metadata( if modified_path: if modified_path == resource_path: item['pathMatches'] = True - if ffprobeMetadata.get('import', False): # Add in ffprobe metadata to the system - ffmetadata = datasetFolder.get('meta', {}).get('ffprobe_info', {}) + # add in DIVE Keys: + item['DIVE_DatasetId'] = str(datasetFolder['_id']) + item['DIVE_Name'] = datasetFolder['lowerName'] + datasetFolder.get('name') + if ffprobeMetadata.get( + 'import', False + ): # Add in ffprobe metadata to the system + ffmetadata = datasetFolder.get('meta', {}).get( + 'ffprobe_info', {} + ) ffkeys = ffprobeMetadata.get('keys', []) for ffMetadataKey in ffkeys: if ffmetadata.get(ffMetadataKey, False): - item[f'ffprobe_{ffMetadataKey}'] = ffmetadata.get(ffMetadataKey, False) + item[f'ffprobe_{ffMetadataKey}'] = ffmetadata.get( + ffMetadataKey, False + ) DIVE_Metadata().createMetadata( datasetFolder, folder, user, item ) @@ -340,6 +359,143 @@ def process_metadata( "metadataKeys": metadataKeys, } + @access.user + @autoDescribeRoute( + Description("Processing a folder and any children folder that have a specified format") + .modelParam( + "id", + description="Parent Folder of where to add the new metadata folder", + model=Folder, + level=AccessType.WRITE, + ) + .param( + "name", + description="Metadata Folder Name", + paramType="formData", + dataType="string", + default='New Metadata Folder Name', + required=False, + ) + .param( + "rootFolderId", + "Root folder to search for all files underneath and crate metadata entries for all of them", + paramType="formData", + dataType="string", + required=True, + ) + .jsonParam( + "displayConfig", + "List of Main Display Keys for the metadata and keys to hide from the filter", + required=True, + default={ + "display": ['DIVE_DatasetId', 'DIVE_Name'], + "hide": [""], + }, + ) + .jsonParam( + "ffprobeMetadata", + "List Metadata keys to extract from the ffprobe metadata from videos. Setting 'import' to 'true' will import the data", + required=True, + default={ + "import": True, + "keys": ["width", "height", "display_aspect_ratio"], + }, + ) + .param( + "categoricalLimit", + "Above this number make a field a search field instead of a dropdown", + paramType="formData", + dataType="integer", + default=50, + ) + ) + def create_metadata_folder( + self, folder, name, rootFolderId, displayConfig, ffprobeMetadata, categoricalLimit + ): + # Process the current folder for the specified fileType using the matcher to generate DIVE_Metadata + # make sure the folder is set to a DIVE Metadata folder using DIVE_METADATA = True + user = self.getCurrentUser() + # first determine the search folder for the system + search_folder = rootFolderId + + base_folder = Folder().createFolder(folder, name) + data = None + errorLog = [] + added = 0 + metadataKeys = {} + datasetList = [] + rootFolder = Folder().load( + rootFolderId, + level=AccessType.READ, + user=user, + force=True, + ) + crud_dataset.get_recursive_datasets(rootFolder, user, datasetList) + + for item in datasetList: + data = {} + data['DIVE_DatasetId'] = str(item['_id']) + data['DIVE_Name'] = str(item['lowerName']) + if ffprobeMetadata.get('import', False): # Add in ffprobe metadata to the system + ffmetadata = item.get('meta', {}).get('ffprobe_info', {}) + ffkeys = ffprobeMetadata.get('keys', []) + for ffMetadataKey in ffkeys: + if ffmetadata.get(ffMetadataKey, False): + data[f'ffprobe_{ffMetadataKey}'] = ffmetadata.get(ffMetadataKey, False) + DIVE_Metadata().createMetadata(item, base_folder, user, data) + for key in data.keys(): + if key not in metadataKeys.keys() and data[key] is not None: + datatype = python_to_javascript_type(type(data[key])) + metadataKeys[key] = {"type": datatype, "set": set(), "count": 0} + if data[key] is None: + continue # we skip null values for processing + if metadataKeys[key]['type'] == 'string': + metadataKeys[key]['set'].add(data[key]) + metadataKeys[key]['count'] += 1 + if metadataKeys[key]['type'] == 'array': + for arrayitem in data[key]: + if python_to_javascript_type(type(arrayitem)) == 'string': + metadataKeys[key]['set'].add(arrayitem) + metadataKeys[key]['count'] += 1 + if metadataKeys[key]['type'] == 'number': + if 'range' not in metadataKeys[key].keys(): + metadataKeys[key]['range'] = {"min": data[key], "max": data[key]} + metadataKeys[key]['range'] = { + "min": min(data[key], metadataKeys[key]["range"]["min"]), + "max": max(data[key], metadataKeys[key]["range"]["max"]), + } + + # now we need to determine what is categorical vs what is a search field + for key in metadataKeys.keys(): + item = metadataKeys[key] + metadataKeys[key]["unique"] = len(item["set"]) + if item["type"] in ['string', 'array'] and ( + item["unique"] < categoricalLimit + or (item["count"] <= len(item["set"]) and len(item["set"]) < categoricalLimit) + ): + metadataKeys[key]["category"] = "categorical" + metadataKeys[key]['set'] = list(metadataKeys[key]['set']) + elif item["type"] == 'string': + metadataKeys[key]["category"] = "search" + del metadataKeys[key]['set'] + elif item["type"] == 'number': + metadataKeys[key]["category"] = "numerical" + del metadataKeys[key]['set'] + else: + del metadataKeys[key]['set'] + DIVE_MetadataKeys().createMetadataKeys(base_folder, user, metadataKeys) + # add metadata to root folder for + base_folder['meta'][DIVEMetadataMarker] = True + displayConfig['categoricalLimit'] = categoricalLimit + base_folder['meta'][DIVEMetadataFilter] = displayConfig + Folder().save(base_folder) + + return { + "results": f"added {added} folders", + "errors": errorLog, + "metadataKeys": metadataKeys, + } + @access.user @autoDescribeRoute( Description( @@ -361,6 +517,16 @@ def get_metadata_keys( query=query, user=user, ) + keys = metadata_key['metadataKeys'] + for key in keys: + item = keys[key] + if item['category'] == 'numerical': + if ( + item['range'] + and item['range']['min'] == float('inf') + or item['range']['max'] == float('-inf') + ): + item['range'] = {'min': 0, 'max': 0} return metadata_key @access.user @@ -548,42 +714,41 @@ def get_metadata_filter(self, folder, keys=None): @access.user @autoDescribeRoute( - Description("Delete Folder VideoState").modelParam( - "rootId", + Description("Delete Folder Metadata").modelParam( + "root", description="FolderId to get state from", model=Folder, level=AccessType.READ, - destName="rootId", + destName="root", ) ) - def delete_metadata(self, rootId): + def delete_metadata(self, root): user = self.getCurrentUser() - query = {"root": str(rootId["_id"])} + query = {"root": str(root["_id"])} found = DIVE_Metadata().findOne(query=query, user=user) if found: DIVE_Metadata().removeWithQuery(query) DIVE_MetadataKeys().removeWithQuery(query) - rootId = Folder().setMetadata( - rootId, {DIVEMetadataMarker: None, DIVEMetadataFilter: None} - ) - Folder().save(rootId) + root = Folder().setMetadata(root, {DIVEMetadataMarker: None, DIVEMetadataFilter: None}) + Folder().save(root) else: raise RestException('Could not find a state to delete') @access.user @autoDescribeRoute( - Description("Delete Metadata Key from Metadata Folder").modelParam( + Description("Delete Metadata Key from Metadata Folder") + .modelParam( "rootId", description="Root metadata FolderId", model=Folder, level=AccessType.READ, destName="rootId", - ).param( + ) + .param( "key", "Metadata key to remove", required=True, ) - ) def delete_metadata_key(self, rootId, key): user = self.getCurrentUser() @@ -596,23 +761,27 @@ def delete_metadata_key(self, rootId, key): raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') @autoDescribeRoute( - Description("Add Metadata Key to Metdata Folder").modelParam( - "rootId", + Description("Add Metadata Key to Metdata Folder") + .modelParam( + "root", description="Root metadata FolderId", model=Folder, level=AccessType.READ, - destName="rootId", - ).param( + destName="root", + ) + .param( "key", "Metadata key to add", required=False, - ).param( + ) + .param( "category", "type of metadata to add", enum=['numerical', 'categorical', 'search', 'boolean'], required=True, default='numerical', - ).param( + ) + .param( "unlocked", "If this value for each metadata item should be modified by regular users", dataType='boolean', @@ -623,83 +792,87 @@ def delete_metadata_key(self, rootId, key): "values", "List of values, either numbers for numerical category or string for categorical, for search this field isn't required. I.E ['key1', 'key2'] or [0, 20]", required=False, - default=[] + default=[], ) ) - def add_metadata_key(self, rootId, key, category, unlocked, values=[]): + def add_metadata_key(self, root, key, category, unlocked, values=[]): user = self.getCurrentUser() - query = {"root": str(rootId["_id"])} + query = {"root": str(root["_id"])} found = DIVE_Metadata().findOne(query=query, user=user) if found: - info= { "count": 0, "category": category} + info = {"count": 0, "category": category} if category == 'categorical': info['set'] = set(values) if category == 'numerical': - info['range'] = { 'min': float('inf'), 'max': float('-inf')} - DIVE_MetadataKeys().addKey(rootId["_Id"], user, key, info, unlocked) - Folder().save(rootId) + info['range'] = {'min': float('inf'), 'max': float('-inf')} + DIVE_MetadataKeys().addKey(root, user, key, info, unlocked) + Folder().save(root) else: - raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') - + raise RestException(f'Could not Metadata for FolderId: {root["_id"]} to delete key.') + @autoDescribeRoute( - Description("Add Metadata Key to Metdata Folder").modelParam( - "rootId", + Description("Add Metadata Key to Metdata Folder") + .modelParam( + "root", description="Root metadata FolderId", model=Folder, level=AccessType.READ, - destName="rootId", - ).param( + destName="root", + ) + .param( "key", "Metadata key to add", required=False, - ).param( + ) + .param( "unlocked", "If this value for each metadata item should be modified by regular users", dataType='boolean', required=True, default=False, ) - ) - def modify_key_permission(self, rootId, key, unlocked): + def modify_key_permission(self, root, key, unlocked): user = self.getCurrentUser() - query = {"root": str(rootId["_id"])} + query = {"root": str(root["_id"])} found = DIVE_Metadata().findOne(query=query, user=user) if found: - DIVE_MetadataKeys().modifyKeyPermission(rootId["_Id"], user, key, unlocked) - Folder().save(rootId) + DIVE_MetadataKeys().modifyKeyPermission(root, user, key, unlocked) + Folder().save(root) else: - raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') - + raise RestException(f'Could not Metadata for FolderId: {root["_id"]} to delete key.') @autoDescribeRoute( - Description("Set MetadataKey value for a folder").modelParam( - "folder", + Description("Set MetadataKey value for a folder") + .modelParam( + "divedataset", description="The folder to set the key on", model=Folder, - level=AccessType.READ, - destName="folder", - ).param( + level=AccessType.WRITE, + destName="divedataset", + ) + .param( "key", "Metadata key to add", required=False, - ).param( + ) + .param( "value", "Value to set the key to, empty is a None value", required=True, default=None, ) - ) - def set_key_value(self, folder, key, value): + def set_key_value(self, divedataset, key, value): user = self.getCurrentUser() - query = {"DIVEDataset": str(folder["_id"])} + query = {"DIVEDataset": str(divedataset["_id"])} found = DIVE_Metadata().findOne(query=query, user=user) if found: rootId = found['root'] rootFolder = Folder().load(rootId, user=user) - categoricalLimit = rootFolder['meta'].get(DIVEMetadataFilter, {}).get('categoricalLimit', 50) - DIVE_Metadata().updateKey(folder["id"],rootId["_Id"], user, key, value, categoricalLimit) - Folder().save(rootId) + categoricalLimit = ( + rootFolder['meta'].get(DIVEMetadataFilter, {}).get('categoricalLimit', 50) + ) + DIVE_Metadata().updateKey(divedataset, rootId, user, key, value, categoricalLimit) else: - raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') \ No newline at end of file + raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') diff --git a/server/dive_tasks/tasks.py b/server/dive_tasks/tasks.py index 277929b..d4d6506 100644 --- a/server/dive_tasks/tasks.py +++ b/server/dive_tasks/tasks.py @@ -141,7 +141,9 @@ def convert_video( videostream = list(filter(lambda x: x["codec_type"] == "video", jsoninfo["streams"])) multiple_video_streams = None if len(videostream) != 1: - multiple_video_streams = "More than One video stream found, defaulting to the first stream" + multiple_video_streams = ( + "More than One video stream found, defaulting to the first stream" + ) # Extract average framerate avgFpsString: str = videostream[0]["avg_frame_rate"] @@ -189,7 +191,6 @@ def convert_video( constants.FPSMarker: newAnnotationFps, constants.MarkForPostProcess: False, "ffprobe_info": ffprobe_info, - }, ) return diff --git a/server/dive_utils/metadata/models.py b/server/dive_utils/metadata/models.py index 238fb64..0fbe0d9 100644 --- a/server/dive_utils/metadata/models.py +++ b/server/dive_utils/metadata/models.py @@ -61,8 +61,6 @@ def createMetadata(self, folder, root, owner, metadata, created_date=None, unloc existing['root'] = str(root['_id']) existing = self.save(existing) return existing - - def validate(self, doc): if not doc.get('DIVEDataset') or not isinstance(doc['DIVEDataset'], str): @@ -70,25 +68,27 @@ def validate(self, doc): if 'root' not in doc or not isinstance(doc['root'], str): raise ValidationException('root must be a string') return doc - + def updateKey(self, folder, root, owner, key, value, categoricalLimit=50): existing = self.findOne({'DIVEDataset': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') - query = {'root': str(folder['_id'])} + query = {'root': existing['root']} metadataKeys = DIVE_MetadataKeys().findOne( query=query, - user=owner, + owner=str(owner['_id']), ) if not metadataKeys: raise Exception(f'Could not find the root metadataKeys with folderId: {folder["_id"]}') if key not in metadataKeys['unlocked']: raise Exception(f'Key {key} is not unlocked for this metadata and cannot be modified') - existing['metadata'][key] = value + if metadataKeys['metadataKeys'][key]['category'] == 'numerical': + existing['metadata'][key] = float(value) + else: + existing['metadata'][key] = value self.save(existing) # now we need to update the metadataKey - DIVE_MetadataKeys().updateKeyValue(folder, owner, key, value, categoricalLimit) - + DIVE_MetadataKeys().updateKeyValue(existing['root'], owner, key, value, categoricalLimit) class DIVE_MetadataKeys(Model): @@ -113,6 +113,7 @@ def initialize(self): self.ensureIndices( [ 'root', + 'owner', ( [ ('created', SortDir.ASCENDING), @@ -135,9 +136,11 @@ def createMetadataKeys(self, root, owner, metadataKeys, created_date=None, unloc metadataKeys=metadataKeys, unlocked=[], created=created, + owner=str(owner['_id']), ) else: existing['metadataKeys'] = metadataKeys + existing['owner'] = str(owner['_id']) self.save(existing) return existing @@ -150,7 +153,7 @@ def modifyKeyPermission(self, folder, owner, key, unlocked): existing = self.findOne({'root': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') - if owner and self.owner != owner: + if owner['_id'] and existing['owner'] != str(owner['_id']): raise Exception('Only the Owner can modify key permissions') elif existing: if key not in existing['metadataKeys'].keys(): @@ -164,13 +167,20 @@ def modifyKeyPermission(self, folder, owner, key, unlocked): if not unlocked and key in existing['unlocked']: existing.remove(key) self.save(existing) - - def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": "categorical"}, unlocked=True): + + def addKey( + self, + folder, + owner, + key, + info={"set": set(), "count": 0, "category": "categorical"}, + unlocked=True, + ): # info is {"type": datatype, "set": set(), "count": 0} may include range: {min: number, max: number} existing = self.findOne({'root': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') - if owner and self.owner != owner: + if owner['_id'] and existing['owner'] != str(owner['_id']): raise Exception('Only the Owner can modify key permissions') elif existing: if key in existing['metadataKeys'].keys(): @@ -180,12 +190,12 @@ def addKey(self, folder, owner, key, info={"set": set(), "count": 0, "category": if unlocked and key not in existing.get('unlocked', {}): existing['unlocked'].append(key) self.save(existing) - + def deleteKey(self, folder, owner, key): existing = self.findOne({'root': str(folder['_id'])}) if not existing: raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') - if owner and self.owner != owner: + if owner['_id'] and existing['owner'] != str(owner['_id']): raise Exception('Only the Owner can modify key permissions') elif existing: if key in existing['unlocked']: @@ -196,15 +206,13 @@ def deleteKey(self, folder, owner, key): else: raise Exception(f'Key: {key} not found in the current metdata') - - - def updateKeyValue(self, folder, owner, key, value, categoricalLimit): - existing = self.findOne({'root': str(folder['_id'])}) + def updateKeyValue(self, folderId, owner, key, value, categoricalLimit): + existing = self.findOne({'root': folderId}) if not existing: - raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + raise Exception(f'Note MetadataKeys with folderId: {folderId} not found') if key not in existing['metadataKeys'].keys(): raise Exception(f'Key: {key} is not in the metadata') - keyData = existing['metdataKeys'][key] + keyData = existing['metadataKeys'][key] category = keyData['category'] if category == 'categorical': if len(keyData['set']) + 1 < categoricalLimit: @@ -214,9 +222,8 @@ def updateKeyValue(self, folder, owner, key, value, categoricalLimit): del keyData['set'] if category == 'numerical' and keyData.get('range', False): range = keyData['range'] - range['min'] = min(value, range['min']) - range['max'] = max(value, range['max']) + range['min'] = min(float(value), float(range['min'])) + range['max'] = max(float(value), float(range['max'])) keyData['range'] = range - existing['metdataKeys'][key] = keyData + existing['metadataKeys'][key] = keyData self.save(existing) - diff --git a/server/poetry.lock b/server/poetry.lock index a2ca2b4..06fae99 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -681,6 +681,26 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "docker" version = "7.1.0" @@ -855,12 +875,12 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "girder" -version = "3.2.3" +version = "3.2.6" description = "Web-based data management platform" optional = false python-versions = ">=3.8" files = [ - {file = "girder-3.2.3.tar.gz", hash = "sha256:6f1d31c26237bc5b6f4010692248231c7c9a4d2a75701ec761a41d6e75de8fcb"}, + {file = "girder-3.2.6.tar.gz", hash = "sha256:22d1dd4d5741328036c3032d2aa609f6a735343950437bf24aefaef72c92e2af"}, ] [package.dependencies] @@ -875,7 +895,7 @@ jsonschema = "*" Mako = "*" passlib = {version = "*", extras = ["bcrypt", "totp"]} psutil = "*" -pymongo = ">=3.6,<4" +pymongo = ">=4" pyOpenSSL = "*" python-dateutil = "*" pytz = "*" @@ -888,12 +908,12 @@ sftp = ["paramiko"] [[package]] name = "girder-client" -version = "3.2.3" +version = "3.2.6" description = "Python client for interacting with Girder servers" optional = false python-versions = ">=3.8" files = [ - {file = "girder-client-3.2.3.tar.gz", hash = "sha256:18433114045d597082301829a7e20451335260c226f01fd2997e3247a2876556"}, + {file = "girder_client-3.2.6.tar.gz", hash = "sha256:9a3e8d103a2e653a458ebfd6136b073222f804620b276ade47404df1487637bc"}, ] [package.dependencies] @@ -904,12 +924,12 @@ requests_toolbelt = "*" [[package]] name = "girder-jobs" -version = "3.2.3" +version = "3.2.6" description = "A general purpose plugin for managing offline jobs." optional = false python-versions = ">=3.8" files = [ - {file = "girder-jobs-3.2.3.tar.gz", hash = "sha256:14a5a55138477dee260a0f22aac83ffb4daf622a62816a3f937750a5428c5d8d"}, + {file = "girder_jobs-3.2.6.tar.gz", hash = "sha256:ab5bdcc0937604e09a6a1f7f052456ac6649f32ef32f08c5a42c48a151c2e9c1"}, ] [package.dependencies] @@ -965,12 +985,12 @@ girder = ["girder (>=3.0.1,<5)", "girder-jobs (>=3.0.1,<5)"] [[package]] name = "girder-worker-utils" -version = "0.9.0" +version = "0.9.1" description = "Helper utilities for the Girder Worker" optional = false python-versions = ">=3.8" files = [ - {file = "girder-worker-utils-0.9.0.tar.gz", hash = "sha256:ee1a2d9470d623d14db9cecd82e6c8f1d4f612fc8e15d6725ee23b5fd60a8fb4"}, + {file = "girder_worker_utils-0.9.1.tar.gz", hash = "sha256:9e72a4f28458cd67f8ce5b7b4f68159630dac5cc74501f5ae62ee954a8ad12a1"}, ] [package.dependencies] @@ -1882,130 +1902,83 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pymongo" -version = "3.13.0" +version = "4.9.1" description = "Python driver for MongoDB " optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "pymongo-3.13.0-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:3ad3a3df830f7df7e0856c2bdb54d19f5bf188bd7420985e18643b8e4d2a075f"}, - {file = "pymongo-3.13.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b96e0e9d2d48948240b510bac81614458fc10adcd3a93240c2fd96448b4efd35"}, - {file = "pymongo-3.13.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f592b202d77923498b32ddc5b376e5fa9ba280d3e16ed56cb8c932fe6d6a478"}, - {file = "pymongo-3.13.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:851f2bb52b5cb2f4711171ca925e0e05344a8452972a748a8a8ffdda1e1d72a7"}, - {file = "pymongo-3.13.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1c9d23f62a3fa7523d849c4942acc0d9ff7081ebc00c808ee7cfdc070df0687f"}, - {file = "pymongo-3.13.0-cp27-cp27m-win32.whl", hash = "sha256:a17b81f22398e3e0f72bdf938e98c810286994b2bcc0a125cd5ad8fd4ea54ad7"}, - {file = "pymongo-3.13.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4f6dd55dab77adf60b445c11f426ee5cdfa1b86f6d54cb937bfcbf09572333ab"}, - {file = "pymongo-3.13.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:776f90bf2252f90a4ae838e7917638894c6356bef7265f424592e2fd1f577d05"}, - {file = "pymongo-3.13.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:50b99f4d3eee6f03778fe841d6f470e6c18e744dc665156da6da3bc6e65b398d"}, - {file = "pymongo-3.13.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50a81b2d9f188c7909e0a1084fa969bb92a788076809c437ac1ae80393f46df9"}, - {file = "pymongo-3.13.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c7c45a8a1a752002b0a7c81ab3a4c5e3b6f67f9826b16fbe3943f5329f565f24"}, - {file = "pymongo-3.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1037097708498bdc85f23c8798a5c46c7bce432d77d23608ff14e0d831f1a971"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:b5b733694e7df22d5c049581acfc487695a6ff813322318bed8dd66f79978636"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d7c91747ec8dde51440dd594603158cc98abb3f7df84b2ed8a836f138285e4fb"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:f4175fcdddf764d371ee52ec4505a40facee2533e84abf2953cda86d050cfa1f"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:93d4e9a02c17813b34e4bd9f6fbf07310c140c8f74341537c24d07c1cdeb24d1"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:3b261d593f2563299062733ae003a925420a86ff4ddda68a69097d67204e43f3"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:172db03182a22e9002157b262c1ea3b0045c73d4ff465adc152ce5b4b0e7b8d4"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09de3bfc995ae8cb955abb0c9ae963c134dba1b5622be3bcc527b89b0fd4091c"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0379447587ee4b8f983ba183202496e86c0358f47c45612619d634d1fcd82bd"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30245a8747dc90019a3c9ad9df987e0280a3ea632ad36227cde7d1d8dcba0830"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6fddf6a7b91da044f202771a38e71bbb9bf42720a406b26b25fe2256e7102"}, - {file = "pymongo-3.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5831a377d15a626fbec10890ffebc4c6abcd37e4126737932cd780a171eabdc1"}, - {file = "pymongo-3.13.0-cp310-cp310-win32.whl", hash = "sha256:944249aa83dee314420c37d0f40c30a8f6dc4a3877566017b87062e53af449f4"}, - {file = "pymongo-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea8824ebc9a1a5c8269e8f1e3989b5a6bec876726e2f3c33ebd036cb488277f0"}, - {file = "pymongo-3.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdd34c57b4da51a7961beb33645646d197e41f8517801dc76b37c1441e7a4e10"}, - {file = "pymongo-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f9cc42a162faa241c82e117ac85734ae9f14343dc2df1c90c6b2181f791b22"}, - {file = "pymongo-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a82a1c10f5608e6494913faa169e213d703194bfca0aa710901f303be212414"}, - {file = "pymongo-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8927f22ef6a16229da7f18944deac8605bdc2c0858be5184259f2f7ce7fd4459"}, - {file = "pymongo-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6f8191a282ef77e526f8f8f63753a437e4aa4bc78f5edd8b6b6ed0eaebd5363"}, - {file = "pymongo-3.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d9ed67c987bf9ac2ac684590ba3d2599cdfb0f331ee3db607f9684469b3b59d"}, - {file = "pymongo-3.13.0-cp311-cp311-win32.whl", hash = "sha256:e8f6979664ff477cd61b06bf8aba206df7b2334209815ab3b1019931dab643d6"}, - {file = "pymongo-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:174fd1000e896d0dfbc7f6d7e6a1992a4868796c7dec31679e38218c78d6a942"}, - {file = "pymongo-3.13.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:d1ee773fb72ba024e7e3bb6ea8907fe52bccafcb5184aaced6bad995bd30ea20"}, - {file = "pymongo-3.13.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:28565e3dbd69fe5fe35a210067064dbb6ed5abe997079f653c19c873c3896fe6"}, - {file = "pymongo-3.13.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5c1db7d366004d6c699eb08c716a63ae0a3e946d061cbebea65d7ce361950265"}, - {file = "pymongo-3.13.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1956f3338c10308e2f99c2c9ff46ae412035cbcd7aaa76c39ccdb806854a247"}, - {file = "pymongo-3.13.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:10f0fddc1d63ba3d4a4bffcc7720184c1b7efd570726ad5e2f55818da320239f"}, - {file = "pymongo-3.13.0-cp35-cp35m-win32.whl", hash = "sha256:570ae3365b23d4fd8c669cb57613b1a90b2757e993588d3370ef90945dbeec4b"}, - {file = "pymongo-3.13.0-cp35-cp35m-win_amd64.whl", hash = "sha256:79f777eaf3f5b2c6d81f9ef00d87837001d7063302503bbcbfdbf3e9bc27c96f"}, - {file = "pymongo-3.13.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d42eb29ba314adfd9c11234b4b646f61b0448bf9b00f14db4b317e6e4b947e77"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e5e87c0eb774561c546f979342a8ff36ebee153c60a0b6c6b03ba989ceb9538c"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0f2c5a5984599a88d087a15859860579b825098b473d8c843f1979a83d159f2e"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:59c98e86c5e861032b71e6e5b65f23e6afaacea6e82483b66f1191a5021a7b4f"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:70b67390e27e58876853efbb87e43c85252de2515e2887f7dd901b4fa3d21973"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:42ba8606492d76e6f9e4c7a458ed4bc712603be393259a52450345f0945da2cf"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:0e5536994cf2d8488c6fd9dea71df3c4dbb3e0d2ba5e695da06d9142a29a0969"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:fe8194f107f0fa3cabd14e9e809f174eca335993c1db72d1e74e0f496e7afe1f"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d593d50815771f517d3ac4367ff716e3f3c78edae51d98e1e25791459f8848ff"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5136ebe8da6a1604998a8eb96be55935aa5f7129c41cc7bddc400d48e8df43be"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a424bdedfd84454d2905a861e0d4bb947cc5bd024fdeb3600c1a97d2be0f4255"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5161167b3840e9c84c80f2534ea6a099f51749d5673b662a3dd248be17c3208"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644470442beaf969df99c4e00367a817eee05f0bba5d888f1ba6fe97b5e1c102"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2406df90b2335371706c59b7d79e9633b81ed2a7ecd48c1faf8584552bdf2d90"}, - {file = "pymongo-3.13.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:222591b828de10ac90064047b5d4916953f38c38b155009c4b8b5e0d33117c2b"}, - {file = "pymongo-3.13.0-cp36-cp36m-win32.whl", hash = "sha256:7cb987b199fa223ad78eebaa9fbc183d5a5944bfe568a9d6f617316ca1c1f32f"}, - {file = "pymongo-3.13.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6cbb73d9fc2282677e2b7a137d13da987bd0b13abd88ed27bba5534c226db06"}, - {file = "pymongo-3.13.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:b1223b826acbef07a7f5eb9bf37247b0b580119916dca9eae19d92b1290f5855"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:398fb86d374dc351a4abc2e24cd15e5e14b2127f6d90ce0df3fdf2adcc55ac1b"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9c3d07ea19cd2856d9943dce37e75d69ecbb5baf93c3e4c82f73b6075c481292"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:2943d739715f265a2983ac43747595b6af3312d0a370614040959fd293763adf"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:c3b70ed82f20d18d22eafc9bda0ea656605071762f7d31f3c5afc35c59d3393b"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:7ec2bb598847569ae34292f580842d37619eea3e546005042f485e15710180d5"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:8cc37b437cba909bef06499dadd91a39c15c14225e8d8c7870020049f8a549fe"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:65a063970e15a4f338f14b820561cf6cdaf2839691ac0adb2474ddff9d0b8b0b"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02f0e1a75d3bc0e16c7e15daf9c56185642be055e425f3b34888fc6eb1b22401"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e74b9c2aca2734c7f49f00fe68d6830a30d26df60e2ace7fe40ccb92087b94"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24e954be35ad4537840f20bbc8d75320ae647d3cb4fab12cb8fcd2d55f408e76"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a149377d1ff766fd618500798d0d94637f66d0ae222bb6d28f41f3e15c626297"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61660710b054ae52c8fc10368e91d74719eb05554b631d7f8ca93d21d2bff2e6"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bbc0d27dfef7689285e54f2e0a224f0c7cd9d5c46d2638fabad5500b951c92f"}, - {file = "pymongo-3.13.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9b2ed9c3b30f11cd4a3fbfc22167af7987b01b444215c2463265153fe7cf66d6"}, - {file = "pymongo-3.13.0-cp37-cp37m-win32.whl", hash = "sha256:1c2c5e2b00e2fadcd590c0b2e293d71215e98ed1cb635cfca2be4998d197e534"}, - {file = "pymongo-3.13.0-cp37-cp37m-win_amd64.whl", hash = "sha256:32eac95bbb030b2376ffd897376c6f870222a3457f01a9ce466b9057876132f8"}, - {file = "pymongo-3.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a796ef39dadf9d73af05d24937644d386495e43a7d13617aa3651d836da542c8"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b6793baf4639c72a500698a49e9250b293e17ae1faf11ac1699d8141194786fe"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:80d8576b04d0824f63bf803190359c0d3bcb6e7fa63fefbd4bc0ceaa7faae38c"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:db2e11507fe9cc2a722be21ccc62c1b1295398fe9724c1f14900cdc7166fc0d7"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:b01ce58eec5edeededf1992d2dce63fb8565e437be12d6f139d75b15614c4d08"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d1a19d6c5098f1f4e11430cd74621699453cbc534dd7ade9167e582f50814b19"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:7219b1a726ced3bacecabef9bd114529bbb69477901373e800d7d0140baadc95"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:2dae3b353a10c3767e0aa1c1492f2af388f1012b08117695ab3fd1f219e5814e"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12721d926d43d33dd3318e58dce9b0250e8a9c6e1093fa8e09f4805193ff4b43"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6af0a4b17faf26779d5caee8542a4f2cba040cea27d3bffc476cbc6ccbd4c8ee"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b9d0f5a445c7e0ddcc021b09835aa6556f0166afc498f57dfdd72cdf6f02ad"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db5b4f8ad8607a3d612da1d4c89a84e4cf5c88f98b46365820d9babe5884ba45"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dbf5fecf653c152edb75a35a8b15dfdc4549473484ee768aeb12c97983cead"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34cd48df7e1fc69222f296d8f69e3957eb7c6b5aa0709d3467184880ed7538c0"}, - {file = "pymongo-3.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c8f755ff1f4ab4ca790d1d6d3229006100b301475948021b6b2757822e0d6c97"}, - {file = "pymongo-3.13.0-cp38-cp38-win32.whl", hash = "sha256:b0746d0d4535f56bbaa63a8f6da362f330804d578e66e126b226eebe76c2bf00"}, - {file = "pymongo-3.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ad0515abb132f52ce9d8abd1a29681a1e65dba7b7fe13ea01e1a8db5715bf80"}, - {file = "pymongo-3.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3c5cb6c93c94df76a879bad4b89db0104b01806d17c2b803c1316ba50962b6d6"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2e0854170813238f0c3131050c67cb1fb1ade75c93bf6cd156c1bd9a16095528"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1410faa51ce835cc1234c99ec42e98ab4f3c6f50d92d86a2d4f6e11c97ee7a4e"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d7910135f5de1c5c3578e61d6f4b087715b15e365f11d4fa51a9cee92988b2bd"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:028175dd8d2979a889153a2308e8e500b3df7d9e3fd1c33ca7fdeadf61cc87a2"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:2bfc39276c0e6d07c95bd1088b5003f049e986e089509f7dbd68bb7a4b1e65ac"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:4092b660ec720d44d3ca81074280dc25c7a3718df1b6c0fe9fe36ac6ed2833e4"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:5bdeb71a610a7b801416268e500e716d0fe693fb10d809e17f0fb3dac5be5a34"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3bca8e76f5c00ed2bb4325e0e383a547d71595926d5275d7c88175aaf7435e"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c7cab8155f430ca460a6fc7ae8a705b34f3e279a57adb5f900eb81943ec777c"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a32f3dfcca4a4816373bdb6256c18c78974ebb3430e7da988516cd95b2bd6e4"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ed2788a6ec68743e2040ab1d16573d7d9f6e7333e45070ce9268cbc93d148c"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e61a536ffed84d10376c21c13a6ed1ebefb61989a844952547c229d6aeedf3"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0665412dce26b2318092a33bd2d2327d487c4490cfcde158d6946d39b1e28d78"}, - {file = "pymongo-3.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:64ed1a5ce5e5926727eb0f87c698c4d9a7a9f7b0953683a65e9ce2b7cc5f8e91"}, - {file = "pymongo-3.13.0-cp39-cp39-win32.whl", hash = "sha256:7593cb1214185a0c5b43b96effc51ce82ddc933298ee36db7dc2bd45d61b4adc"}, - {file = "pymongo-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:3cfc9bc1e8b5667bc1f3dbe46d2f85b3f24ff7533893bdc1203058012db2c046"}, - {file = "pymongo-3.13.0.tar.gz", hash = "sha256:e22d6cf5802cd09b674c307cc9e03870b8c37c503ebec3d25b86f2ce8c535dc7"}, -] + {file = "pymongo-4.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc3d070d746ab79e9b393a5c236df20e56607389af2b79bf1bfe9a841117558e"}, + {file = "pymongo-4.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe709d05654c12fc513617c8d5c8d05b7e9cf1d5d94ada68add4e89530c867d2"}, + {file = "pymongo-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa4493f304b33c5d2ecee3055c98889ac6724d56f5f922d47420a45d0d4099c9"}, + {file = "pymongo-4.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8e8b8deba6a4bff3dd5421071083219521c74d2acae0322de5c06f1a66c56af"}, + {file = "pymongo-4.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3645aff8419ca60f9ccd08966b2f6b0d78053f9f98a814d025426f1d874c19a"}, + {file = "pymongo-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51dbc6251c6783dfcc7d657c346986d8bad7210989b2fe15de16db5204a8e7ae"}, + {file = "pymongo-4.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d7aa9cc2d92e73bdb036c578ba019da94ea165eb147e691cd910a6fab7ce3b7"}, + {file = "pymongo-4.9.1-cp310-cp310-win32.whl", hash = "sha256:8b632e01617f2608880f7b9926f54a5f5ebb51631996e0540fff7fc7980663c9"}, + {file = "pymongo-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:f05e34d401be871d7c87cb10727d49315444e4ded07ff876a595e4c23b7436da"}, + {file = "pymongo-4.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bb3d5282278594753089dc7da48bfae4a7f337a2dd4d397eabb591c649e58d0"}, + {file = "pymongo-4.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f0d5258bc85a4e6b5bcae8160628168e71ec4625a58ceb53327c3280a0b6914"}, + {file = "pymongo-4.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96462fb2175f740701d229f52018ea6e4adc4148c4112e6628bb359dd534a3df"}, + {file = "pymongo-4.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:286fb275267f0293364ba579f6354452599161f1902ad411061c7f744ab88328"}, + {file = "pymongo-4.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cddb51cead9700c4dccc916952bc0321b8d766bf782d374bfa0e93ef47c1d20"}, + {file = "pymongo-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d79f20f9c7cbc1c708fb80b648b6fbd3220fd3437a9bd6017c1eb592e03b361"}, + {file = "pymongo-4.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd3352eaf578f8e9bdea7a5692910eedad1e8680f60726fc70e99c8af51a5449"}, + {file = "pymongo-4.9.1-cp311-cp311-win32.whl", hash = "sha256:ea3f0196e7c311b9944a609ac175bd91ab97952164a1246716fdd38d53ca3bcc"}, + {file = "pymongo-4.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4c793db8457c856f333f396798470b9bfe405e17c307d581532c74cec70150c"}, + {file = "pymongo-4.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:47b4896544095d172c366dd4d4ea1da6b0ab1a77d8416897cc1801e2421b1e67"}, + {file = "pymongo-4.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbb1c7dfcf6c44e9e1928290631c7603817991cdf570691c9e15fca594918435"}, + {file = "pymongo-4.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7689da1d1b444284e4ea9ab2eb64a15307b6b795918c0f3cd7774dd1d8a7556"}, + {file = "pymongo-4.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f962d74201c772555f7a78792fed820a5ea76db5c7ee6cf43748e411b44e430"}, + {file = "pymongo-4.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08fbab69f3fb6f8088c81f4c4a8abd84a99c132034f5e27e47f894bbcb6bf439"}, + {file = "pymongo-4.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4327c0d9bd616b8289691360f2d4a09a72fe35479795832eae0d4ff78af53923"}, + {file = "pymongo-4.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34e4993ae78be56f9e27a141168a1ab78253576fa3e893fa335a719ce204c3ef"}, + {file = "pymongo-4.9.1-cp312-cp312-win32.whl", hash = "sha256:e1f346811d4a2369f88ab7a6f886fa9c3bbc9ed4e4f4a3becca8717a73d465cb"}, + {file = "pymongo-4.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:a2b12c74cfd90147babb77f9728646bcedfdbd2bd2a5b4130a00e3a0af1a3d34"}, + {file = "pymongo-4.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a40ea8bc9cffb61c5c9c426c430d22235e085e610ee81ae075ddf51f12f76236"}, + {file = "pymongo-4.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75d5974f874acdb2f125bdbe785045b23a39ecce1d3143dd5712800c7b6d25eb"}, + {file = "pymongo-4.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f23a046531030318622414f21198e232cf93c5640da9a80b45596a059c8cc090"}, + {file = "pymongo-4.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91b1a92214c3912af5467f77c2f6435cd76f6de64c70cba7bb4ee43eba7f459e"}, + {file = "pymongo-4.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a846423c4535428f69a90a1451df3718bc59f0c4ab685b9e96d3071951e0be4"}, + {file = "pymongo-4.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d476d91a5c9e6c37bc8ec3fb294e1c01d95736ccf01a59bb1540fe2f710f826e"}, + {file = "pymongo-4.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:172d8ba0f567e351a18765db23dab7dbcfdffd91a8788d90d46b350f80a40781"}, + {file = "pymongo-4.9.1-cp313-cp313-win32.whl", hash = "sha256:95418e334629440f70fe5ceeefc6cbbd50defb566901c8d68179ffbaec8d5f01"}, + {file = "pymongo-4.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:1dfd2aa30174d36a3ef1dae4ee4c89710c2d65cac52ce6e13f17c710edbd61cf"}, + {file = "pymongo-4.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c4204fad54830a3173a5c939cd052d0561fba03dba7e0ff6852fd631f3314aa4"}, + {file = "pymongo-4.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:375765ec81b1f0a26d08928afea0c3dff897c36080a090be53fc7b70cc51d497"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d1b959a3dda0775d9111622ee47ad47772aed3a9da2e7d5f2f513fa68175dea"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42c19d2b094cdd0ead7dbb38860bbe8268c140334ce55d8b39204ddb4ebd4904"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fac1def9e9073f1c80198c99f0ec39c2528236c8912d96d7fd3b0237f4c523a"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b347052d510989d1f52b8553b31297f21cf74bd9f6aed71ee84e563492f4ff17"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b4b961fce213f2bcdc92268f85111a3668c61b9b4d4e7ece27dce3a137cfcbd"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a0b10cf51ec14a487c94709d294c00e1fb6a0a4c38cdc3acfb2ced5ef60972a0"}, + {file = "pymongo-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:679b8d55854da7c7fdb82aa5e092ab4de0144daf6758defed8ab00ff9ce05360"}, + {file = "pymongo-4.9.1-cp38-cp38-win32.whl", hash = "sha256:432ad395d2233056b042ccc73234e7136aa65d944d6bd8b5138394bd38aaff79"}, + {file = "pymongo-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9fbe9fad27619ac4cfda5df0ade26a99906da7dfe7b01deddc25997eb1804e4c"}, + {file = "pymongo-4.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:99b611ff75b5d9e17183dcf9584a7b04f9db07e51a162f23ea05e485e0735c0a"}, + {file = "pymongo-4.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8089003a99127f917bdbeec177d41cef019cda8ec70534c1018cb60aacd23c2a"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d78adf25967c06298c7e488f4cfab79a390fc32c2b1d428613976f99031603d"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56877cfcdf7dfc5c6408e4551ec0d6d65ebbca4d744a0bc90400f09ef6bbcc8a"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d2efe559d0d96bc0b74b3ff76701ad6f6e1a65f6581b573dcacc29158131c8"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f838f613e74b4dad8ace0d90f42346005bece4eda5bf6d389cfadb8322d39316"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db5b299e11284f8d82ce2983d8e19fcc28f98f902a179709ef1982b4cca6f8b8"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b23211c031b45d0f32de83ab7d77f9c26f1025c2d2c91463a5d8594a16103655"}, + {file = "pymongo-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:687cf70e096381bc65b4273a6a9319617618f7ace65caffc356e1099c4a68511"}, + {file = "pymongo-4.9.1-cp39-cp39-win32.whl", hash = "sha256:e02b03e3815b80a63e773e4c32aed3cf5633d406f376477be74550295c211256"}, + {file = "pymongo-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:0492ef43f3342354cf581712e431621c221f60c877ebded84e3f3e53b71bbbe0"}, + {file = "pymongo-4.9.1.tar.gz", hash = "sha256:b7f2d34390acf60e229c30037d1473fcf69f4536cd7f48f6f78c0c931c61c505"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" [package.extras] -aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] -gssapi = ["pykerberos"] -ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] +docs = ["furo (==2023.9.10)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<3)", "sphinxcontrib-shellcheck (>=1,<2)"] +encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.10.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] -srv = ["dnspython (>=1.16.0,<1.17.0)"] -tls = ["ipaddress"] +test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] zstd = ["zstandard"] [[package]] @@ -2803,4 +2776,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "d94d1fca38eaed435a1677a86b93ba204f7c27e630e0080893718346a1e45369" +content-hash = "770af67d8a67b57c204ba2bb896fadb12d40e6b7b28b9dd134af31c46165d576" diff --git a/server/pyproject.toml b/server/pyproject.toml index 58ca267..664fa41 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -49,10 +49,11 @@ packages = [ python = ">=3.8,<3.12" cheroot = ">=8.4.5" click = "^8.1.3" -girder = ">=3.2.3" -girder_jobs = ">=3.2.3" +girder = ">=3.2.6" +girder_jobs = ">=3.2.6" +girder_client = ">=3.2.6" girder_worker = ">=0.10.3" -girder_worker_utils = ">=0.9.0" +girder_worker_utils = ">=0.9.1" pydantic = "1.10.13" pyrabbit2 = "1.0.7" typing-extensions = "^4.2.0" From 1ae47938ff38950dcbf61ba0c0960c609773798a Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Mon, 30 Sep 2024 14:25:30 -0400 Subject: [PATCH 05/14] adding delete key option --- server/dive_server/views_metadata.py | 60 +++++++++++++++++++++++----- server/dive_utils/metadata/models.py | 18 ++++++++- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index 1edec9e..6c943af 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -118,11 +118,19 @@ def __init__(self, resourceName): self.route("POST", (':id', "clone_filter"), self.clone_filter) self.route("GET", (':id', 'metadata_keys'), self.get_metadata_keys) self.route("GET", (':id', 'metadata_filter_values'), self.get_metadata_filter) - self.route("DELETE", (':rootId',), self.delete_metadata) + self.route( + "DELETE", + ( + ':rootId', + 'root_metadata', + ), + self.delete_metadata, + ) self.route("DELETE", (':rootId', 'delete_key'), self.delete_metadata_key) self.route("PUT", (':root', 'add_key'), self.add_metadata_key) self.route("PATCH", (':root', 'modify_key_permission'), self.modify_key_permission) self.route("PATCH", (':divedataset',), self.set_key_value) + self.route("DELETE", (':divedataset',), self.delete_key_value) @access.user @autoDescribeRoute( @@ -752,13 +760,16 @@ def delete_metadata(self, root): ) def delete_metadata_key(self, rootId, key): user = self.getCurrentUser() - query = {"root": str(rootId["_id"])} - found = DIVE_Metadata().findOne(query=query, user=user) + query = {"root": str(rootId["_id"]), "owner": str(user['_id'])} + found = DIVE_MetadataKeys().findOne(query=query, user=user) + print(found) if found: - DIVE_MetadataKeys().deleteKey(rootId["_Id"], user, key) + DIVE_MetadataKeys().deleteKey(rootId, user, key) Folder().save(rootId) else: - raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') + raise RestException( + f'Could not find Metadata for FolderId: {rootId["_id"]} to delete key.' + ) @autoDescribeRoute( Description("Add Metadata Key to Metdata Folder") @@ -797,8 +808,8 @@ def delete_metadata_key(self, rootId, key): ) def add_metadata_key(self, root, key, category, unlocked, values=[]): user = self.getCurrentUser() - query = {"root": str(root["_id"])} - found = DIVE_Metadata().findOne(query=query, user=user) + query = {"root": str(root["_id"]), "owner": str(user['_id'])} + found = DIVE_MetadataKeys().findOne(query=query) if found: info = {"count": 0, "category": category} if category == 'categorical': @@ -808,7 +819,7 @@ def add_metadata_key(self, root, key, category, unlocked, values=[]): DIVE_MetadataKeys().addKey(root, user, key, info, unlocked) Folder().save(root) else: - raise RestException(f'Could not Metadata for FolderId: {root["_id"]} to delete key.') + raise RestException(f'Could not find for FolderId: {root["_id"]} to delete key.') @autoDescribeRoute( Description("Add Metadata Key to Metdata Folder") @@ -835,12 +846,14 @@ def add_metadata_key(self, root, key, category, unlocked, values=[]): def modify_key_permission(self, root, key, unlocked): user = self.getCurrentUser() query = {"root": str(root["_id"])} - found = DIVE_Metadata().findOne(query=query, user=user) + found = DIVE_MetadataKeys().findOne(query=query, owner=str(user['_id'])) if found: DIVE_MetadataKeys().modifyKeyPermission(root, user, key, unlocked) Folder().save(root) else: - raise RestException(f'Could not Metadata for FolderId: {root["_id"]} to delete key.') + raise RestException( + f'Could not find Metadata for FolderId: {root["_id"]} to delete key.' + ) @autoDescribeRoute( Description("Set MetadataKey value for a folder") @@ -875,4 +888,29 @@ def set_key_value(self, divedataset, key, value): ) DIVE_Metadata().updateKey(divedataset, rootId, user, key, value, categoricalLimit) else: - raise RestException(f'Could not Metadata for FolderId: {rootId["_id"]} to delete key.') + raise RestException(f'Could not find for FolderId: {rootId["_id"]} to delete key.') + + @autoDescribeRoute( + Description("Delete a key from a specific DIVE Dataset") + .modelParam( + "divedataset", + description="The folder to delete the key from", + model=Folder, + level=AccessType.WRITE, + destName="divedataset", + ) + .param( + "key", + "Metadata key to delete", + required=False, + ) + ) + def delete_key_value(self, divedataset, key): + user = self.getCurrentUser() + query = {"DIVEDataset": str(divedataset["_id"])} + found = DIVE_Metadata().findOne(query=query, user=user) + if found: + rootId = found['root'] + DIVE_Metadata().deleteKey(divedataset, rootId, user, key) + else: + raise RestException(f'Could not find for FolderId: {divedataset["_id"]} to delete key.') diff --git a/server/dive_utils/metadata/models.py b/server/dive_utils/metadata/models.py index 0fbe0d9..32c396b 100644 --- a/server/dive_utils/metadata/models.py +++ b/server/dive_utils/metadata/models.py @@ -53,12 +53,13 @@ def createMetadata(self, folder, root, owner, metadata, created_date=None, unloc root=str(root['_id']), metadata=metadata, created=created, - owner=owner, + owner=str(owner['_id']), ) else: existing['metadata'] = metadata existing['filename'] = str(folder['name']) existing['root'] = str(root['_id']) + existing['owner'] = str(owner['_id']) existing = self.save(existing) return existing @@ -90,6 +91,20 @@ def updateKey(self, folder, root, owner, key, value, categoricalLimit=50): # now we need to update the metadataKey DIVE_MetadataKeys().updateKeyValue(existing['root'], owner, key, value, categoricalLimit) + def deleteKey(self, folder, root, owner, key): + existing = self.findOne({'DIVEDataset': str(folder['_id'])}) + if not existing: + raise Exception(f'Note MetadataKeys with folderId: {folder["_id"]} found') + query = {'root': existing['root']} + metadataKeys = DIVE_MetadataKeys().findOne( + query=query, + owner=str(owner['_id']), + ) + if not metadataKeys: + raise Exception(f'Could not find the root metadataKeys with folderId: {folder["_id"]}') + del existing['metadata'][key] + self.save(existing) + class DIVE_MetadataKeys(Model): # This is NOT an access controlled model; it is expected that all endpoints @@ -164,6 +179,7 @@ def modifyKeyPermission(self, folder, owner, key, unlocked): self.save(existing) if unlocked and key not in existing.get('unlocked', {}): existing['unlocked'].append(key) + self.save(existing) if not unlocked and key in existing['unlocked']: existing.remove(key) self.save(existing) From ac5aca3293f8581ec7192d93c0acc0430b176982 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Mon, 30 Sep 2024 14:28:27 -0400 Subject: [PATCH 06/14] linting --- server/dive_server/views_metadata.py | 4 +--- server/dive_utils/metadata/models.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index 6c943af..d63e374 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -423,8 +423,6 @@ def create_metadata_folder( # Process the current folder for the specified fileType using the matcher to generate DIVE_Metadata # make sure the folder is set to a DIVE Metadata folder using DIVE_METADATA = True user = self.getCurrentUser() - # first determine the search folder for the system - search_folder = rootFolderId base_folder = Folder().createFolder(folder, name) data = None @@ -806,7 +804,7 @@ def delete_metadata_key(self, rootId, key): default=[], ) ) - def add_metadata_key(self, root, key, category, unlocked, values=[]): + def add_metadata_key(self, root, key, category, unlocked, values=[]): # noqa: B006 user = self.getCurrentUser() query = {"root": str(root["_id"]), "owner": str(user['_id'])} found = DIVE_MetadataKeys().findOne(query=query) diff --git a/server/dive_utils/metadata/models.py b/server/dive_utils/metadata/models.py index 32c396b..b9564cb 100644 --- a/server/dive_utils/metadata/models.py +++ b/server/dive_utils/metadata/models.py @@ -39,7 +39,7 @@ def initialize(self): ] ) - def createMetadata(self, folder, root, owner, metadata, created_date=None, unlocked=[]): + def createMetadata(self, folder, root, owner, metadata, created_date=None): # noqa: B006 existing = self.findOne({'DIVEDataset': str(folder['_id'])}) if not existing: if created_date is None: @@ -138,7 +138,7 @@ def initialize(self): ] ) - def createMetadataKeys(self, root, owner, metadataKeys, created_date=None, unlocked=[]): + def createMetadataKeys(self, root, owner, metadataKeys, created_date=None): existing = self.findOne({'root': str(root['_id'])}) if not existing: if created_date is None: @@ -189,7 +189,7 @@ def addKey( folder, owner, key, - info={"set": set(), "count": 0, "category": "categorical"}, + info={"set": set(), "count": 0, "category": "categorical"}, # noqa: B006 unlocked=True, ): # info is {"type": datatype, "set": set(), "count": 0} may include range: {min: number, max: number} From 92306a1e933e2e47ca73abebdcec1058e82ff3ab Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Wed, 2 Oct 2024 15:27:11 -0400 Subject: [PATCH 07/14] starting client-side dev --- .../web-girder/api/divemetadata.service.ts | 1 + client/platform/web-girder/router.ts | 17 +++ .../web-girder/views/DIVEMetadataFilter.vue | 21 +++ .../web-girder/views/DIVEMetadataSearch.vue | 7 +- .../web-girder/views/DiveMetadataEdit.vue | 131 ++++++++++++++++++ 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 client/platform/web-girder/views/DiveMetadataEdit.vue diff --git a/client/platform/web-girder/api/divemetadata.service.ts b/client/platform/web-girder/api/divemetadata.service.ts index eb91087..b64acaa 100644 --- a/client/platform/web-girder/api/divemetadata.service.ts +++ b/client/platform/web-girder/api/divemetadata.service.ts @@ -35,6 +35,7 @@ export interface DIVEMetadataFilterValueResults { created: string; root: string; metadataKeys: Record; + unlocked: string[]; } export interface DIVEMetadataResults { diff --git a/client/platform/web-girder/router.ts b/client/platform/web-girder/router.ts index 786c372..e646518 100644 --- a/client/platform/web-girder/router.ts +++ b/client/platform/web-girder/router.ts @@ -12,6 +12,7 @@ import DataShared from './views/DataShared.vue'; import DataBrowser from './views/DataBrowser.vue'; import Summary from './views/Summary.vue'; import DIVEMetadataSearchVue from './views/DIVEMetadataSearch.vue'; +import DiveMetadataEditVue from './views/DiveMetadataEdit.vue'; Vue.use(Router); @@ -88,6 +89,22 @@ const router = new Router({ }, beforeEnter, }, + { + path: '/metadata-edit/:id/', + name: 'metadata-edit', + component: DiveMetadataEditVue, + props: (route) => { + if (route.query.filter) { + return { + id: route.params.id, // Map route parameter to prop + }; + } + return { + id: route.params.id, + }; + }, + beforeEnter, + }, { path: '', component: Home, diff --git a/client/platform/web-girder/views/DIVEMetadataFilter.vue b/client/platform/web-girder/views/DIVEMetadataFilter.vue index 169f7ae..7950149 100644 --- a/client/platform/web-girder/views/DIVEMetadataFilter.vue +++ b/client/platform/web-girder/views/DIVEMetadataFilter.vue @@ -43,6 +43,10 @@ export default defineComponent({ default: () => {}, }, + ownerAdmin: { + type: Boolean, + default: false, + }, }, setup(props, { emit }) { const { prompt } = usePrompt(); @@ -236,6 +240,23 @@ export default defineComponent({ Advanced Filters + + + mdi-pencil + + Edit Filters + + Filtered:{{ filtered }} / {{ count }} = ref(props.filter || {}); const locationStore = { _id: props.id, @@ -39,7 +41,7 @@ export default defineComponent({ }; const currentFilter: Ref = ref(props.filter || {}); - + const isOwnerAdmin = ref(false); const processFilteredMetadataResults = (data: DIVEMetadataResults) => { folderList.value = data.pageResults; totalPages.value = data.totalPages; @@ -56,6 +58,9 @@ export default defineComponent({ if (folder.meta.DIVEMetadata) { displayConfig.value = folder.meta.DIVEMetadataFilter; } + if (folder.creatorId === girderRest.user._id || girderRest.user.admin) { + isOwnerAdmin.value = true; + } }; const updateURLParams = () => { diff --git a/client/platform/web-girder/views/DiveMetadataEdit.vue b/client/platform/web-girder/views/DiveMetadataEdit.vue new file mode 100644 index 0000000..b7bcaf8 --- /dev/null +++ b/client/platform/web-girder/views/DiveMetadataEdit.vue @@ -0,0 +1,131 @@ + + + + + From 797673868ef359e36df3293a0f4ca00ea249fba1 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 3 Oct 2024 11:01:31 -0400 Subject: [PATCH 08/14] adding client api endpoints for dive metadata editing --- .../web-girder/api/divemetadata.service.ts | 60 +++++++++++++++++++ .../web-girder/views/DiveMetadataEdit.vue | 46 ++++++++++++++ docs/SlicerCLI.md | 3 +- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/client/platform/web-girder/api/divemetadata.service.ts b/client/platform/web-girder/api/divemetadata.service.ts index b64acaa..82247f4 100644 --- a/client/platform/web-girder/api/divemetadata.service.ts +++ b/client/platform/web-girder/api/divemetadata.service.ts @@ -74,8 +74,68 @@ function createDiveMetadataClone(folder: string, filters: DIVEMetadataFilter, de }); } +function createDiveMetadataFolder( + parentFolder: string, + name: string, + rootFolderId: string, + categoricalLimit = 50, + displayConfig = { + display: ['DIVE_DatasetId', 'DIVE_Name'], + }, + ffprobeMetadata = { + import: true, keys: ['width', 'height', 'display_aspect_ratio'], + }, +) { + return girderRest.post(`dive_metadata/create_metadata_folder/${parentFolder}`, null, { + params: { + name, rootFolderId, categoricalLimit, displayConfig, ffprobeMetadata, + }, + }); +} + +function modifyDiveMetadataPermission(rootMetadataFolder: string, key: string, unlocked: boolean) { + return girderRest.patch(`dive_metadata/${rootMetadataFolder}/modify_key_permission`, null, { + params: { + key, unlocked, + }, + }); +} + +function addDiveMetadataKey(rootMetadataFolder: string, key: string, category: 'numerical' | 'categorical' | 'search' | 'boolean', unlocked = false, values = []) { + return girderRest.put(`dive_metadata/${rootMetadataFolder}/add_key`, null, { + params: { + key, category, unlocked, values, + }, + }); +} + +function deleteDiveMetadataKey(rootMetadataFolder:string, key: string) { + return girderRest.delete(`dive_metadata/${rootMetadataFolder}/delete_key`, { + params: { + key, + }, + }); +} + +function deleteDiveDatasetMetadataKey(diveDatasetId: string, key: string) { + return girderRest.delete(`dive_metadata/${diveDatasetId}`, { params: { key } }); +} +function setDiveDatasetMetadataKey(diveDatasetId: string, key: string, value: number | string | boolean) { + return girderRest.patch(`dive_metadata/${diveDatasetId}`, { + params: { + key, value, + }, + }); +} + export { getMetadataFilterValues, filterDiveMetadata, createDiveMetadataClone, + createDiveMetadataFolder, + modifyDiveMetadataPermission, + addDiveMetadataKey, + deleteDiveMetadataKey, + deleteDiveDatasetMetadataKey, + setDiveDatasetMetadataKey, }; diff --git a/client/platform/web-girder/views/DiveMetadataEdit.vue b/client/platform/web-girder/views/DiveMetadataEdit.vue index b7bcaf8..c948c94 100644 --- a/client/platform/web-girder/views/DiveMetadataEdit.vue +++ b/client/platform/web-girder/views/DiveMetadataEdit.vue @@ -19,6 +19,8 @@ interface FormattedMetadataKeys { range?: { min: number, max: number}, unique?: number, unlocked: boolean, + visible: boolean, + hidden: boolean, } export default defineComponent({ @@ -55,12 +57,15 @@ export default defineComponent({ range: metadataKeys.value[key].range, unique: metadataKeys.value[key].unique, unlocked: unlocked.value.includes(key), + visible: displayConfig.value.display.includes(key), + hidden: displayConfig.value.hide.includes(key), }); } }); }; const metadataHeader = ref([ + { text: 'State', value: 'visibility', width: '75px' }, { text: 'Name', value: 'name' }, { text: 'Category', value: 'category' }, { text: 'Details', value: 'details' }, @@ -81,9 +86,33 @@ export default defineComponent({ getData(); }); + const getEyeState = (item: FormattedMetadataKeys) => { + if (item.visible) { + return { tooltip: 'Item is default visible', icon: 'mdi-eye' }; + } + if (item.hidden) { + return { tooltip: 'Item is hidden', icon: 'mdi-eye-off' }; + } + return { tooltip: 'Item is in the advanced view', icon: 'mdi-eye' }; + }; + const toggleVisibility = (index: number) => { + const item = formattedKeys.value[index]; + if (!item.visible && !item.hidden) { + item.visible = true; + } else if (!item.hidden) { + item.visible = false; + item.hidden = true; + } else { + item.visible = false; + item.hidden = false; + } + }; + return { metadataHeader, formattedKeys, + getEyeState, + toggleVisibility, }; }, }); @@ -102,6 +131,23 @@ export default defineComponent({ :items="formattedKeys" item-key="name" > + - diff --git a/client/yarn.lock b/client/yarn.lock index fda7366..1f27013 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1517,88 +1517,6 @@ estree-walker "^2.0.1" picomatch "^2.2.2" -"@sentry/browser@^5.24.2": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" - integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== - dependencies: - "@sentry/core" "5.30.0" - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" - tslib "^1.9.3" - -"@sentry/cli@^1.77.1": - version "1.77.3" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.77.3.tgz#c40b4d09b0878d6565d42a915855add99db4fec3" - integrity sha512-c3eDqcDRmy4TFz2bFU5Y6QatlpoBPPa8cxBooaS4aMQpnIdLYPF1xhyyiW0LQlDUNc3rRjNF7oN5qKoaRoMTQQ== - dependencies: - https-proxy-agent "^5.0.0" - mkdirp "^0.5.5" - node-fetch "^2.6.7" - progress "^2.0.3" - proxy-from-env "^1.1.0" - which "^2.0.2" - -"@sentry/core@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" - integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== - dependencies: - "@sentry/hub" "5.30.0" - "@sentry/minimal" "5.30.0" - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" - tslib "^1.9.3" - -"@sentry/hub@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" - integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== - dependencies: - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" - tslib "^1.9.3" - -"@sentry/integrations@^5.24.2": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-5.30.0.tgz#2f25fb998cb19c76b3803d84c1a577b3d837010f" - integrity sha512-Fqh4ALLoQWdd+1ih0iBduANWFyNmFWMxwvBu3V/wLDRi8OcquI0lEzWai1InzTJTiNhRHPnhuU++l/vkO0OCww== - dependencies: - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" - localforage "1.8.1" - tslib "^1.9.3" - -"@sentry/minimal@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" - integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== - dependencies: - "@sentry/hub" "5.30.0" - "@sentry/types" "5.30.0" - tslib "^1.9.3" - -"@sentry/types@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" - integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== - -"@sentry/utils@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" - integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== - dependencies: - "@sentry/types" "5.30.0" - tslib "^1.9.3" - -"@sentry/webpack-plugin@^1.18.3": - version "1.21.0" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.21.0.tgz#bbe7cb293751f80246a4a56f9a7dd6de00f14b58" - integrity sha512-x0PYIMWcsTauqxgl7vWUY6sANl+XGKtx7DCVnnY7aOIIlIna0jChTAPANTfA2QrK+VK+4I/4JxatCEZBnXh3Og== - dependencies: - "@sentry/cli" "^1.77.1" - webpack-sources "^2.0.0 || ^3.0.0" - "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -6614,11 +6532,6 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -7861,13 +7774,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lie@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== - dependencies: - immediate "~3.0.5" - lilconfig@^2.0.3, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" @@ -7913,13 +7819,6 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -localforage@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.8.1.tgz#f6c0a24b41ab33b10e4dc84342dd696f6f3e3433" - integrity sha512-azSSJJfc7h4bVpi0PGi+SmLQKJl2/8NErI+LhJsrORNikMZnhaQ7rv9fHj+ofwgSHrKRlsDCL/639a6nECIKuQ== - dependencies: - lie "3.1.1" - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -8223,7 +8122,7 @@ minipass@^3.1.1: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@^0.5.6: +mkdirp@^0.5.1, mkdirp@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -9176,11 +9075,6 @@ progress-webpack-plugin@^1.0.12: figures "^2.0.0" log-update "^2.3.0" -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - proj4@^2.7.5: version "2.11.0" resolved "https://registry.yarnpkg.com/proj4/-/proj4-2.11.0.tgz#795a5790aed30a7535d6a4c5775c0ce2a763cc41" @@ -10714,11 +10608,6 @@ tslib@2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== -tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.0.3, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -11292,7 +11181,7 @@ webpack-merge@^5.7.3: flat "^5.0.2" wildcard "^2.0.0" -"webpack-sources@^2.0.0 || ^3.0.0", webpack-sources@^3.2.3: +webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== @@ -11441,7 +11330,7 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index d63e374..fe64211 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -803,8 +803,15 @@ def delete_metadata_key(self, rootId, key): required=False, default=[], ) + .param( + "default_value", + "If this value for each metadata item should be modified by regular users", + required=False, + default=None, + ) + ) - def add_metadata_key(self, root, key, category, unlocked, values=[]): # noqa: B006 + def add_metadata_key(self, root, key, category, unlocked, values=[], default_value=None): # noqa: B006 user = self.getCurrentUser() query = {"root": str(root["_id"]), "owner": str(user['_id'])} found = DIVE_MetadataKeys().findOne(query=query) @@ -818,6 +825,13 @@ def add_metadata_key(self, root, key, category, unlocked, values=[]): # noqa: B Folder().save(root) else: raise RestException(f'Could not find for FolderId: {root["_id"]} to delete key.') + if default_value is not None: + query = {"root": str(root["_id"])} + existing_data = DIVE_Metadata().find(query) + for item in existing_data: + DIVE_Metadata().updateKey(item['DIVEDataset'], root, user, key, default_value) + + @autoDescribeRoute( Description("Add Metadata Key to Metdata Folder") From ebb9ab65b2109ed89da4fc3978443871e3db0bd4 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Fri, 4 Oct 2024 15:58:41 -0400 Subject: [PATCH 10/14] updating logic --- client/platform/web-girder/views/DiveMetadataEdit.vue | 2 +- server/dive_server/views_metadata.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/platform/web-girder/views/DiveMetadataEdit.vue b/client/platform/web-girder/views/DiveMetadataEdit.vue index cc09583..8f85390 100644 --- a/client/platform/web-girder/views/DiveMetadataEdit.vue +++ b/client/platform/web-girder/views/DiveMetadataEdit.vue @@ -138,7 +138,7 @@ export default defineComponent({ const deleteMetadata = async (index: number) => { const item = formattedKeys.value[index]; if (item) { - deleteDiveMetadataKey(props.id, item.name); + await deleteDiveMetadataKey(props.id, item.name); getFolderInfo(props.id); getData(); } diff --git a/server/dive_server/views_metadata.py b/server/dive_server/views_metadata.py index fe64211..5be4bb7 100644 --- a/server/dive_server/views_metadata.py +++ b/server/dive_server/views_metadata.py @@ -775,7 +775,7 @@ def delete_metadata_key(self, rootId, key): "root", description="Root metadata FolderId", model=Folder, - level=AccessType.READ, + level=AccessType.WRITE, destName="root", ) .param( @@ -829,7 +829,8 @@ def add_metadata_key(self, root, key, category, unlocked, values=[], default_val query = {"root": str(root["_id"])} existing_data = DIVE_Metadata().find(query) for item in existing_data: - DIVE_Metadata().updateKey(item['DIVEDataset'], root, user, key, default_value) + diveDatasetFolder = Folder().load(item['DIVEDataset'], level=AccessType.WRITE, user=user, force=True) + DIVE_Metadata().updateKey(diveDatasetFolder, root, user, key, default_value) From 45c936f5ff8a1c4cd40057c02b94dba69cc64534 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Sat, 5 Oct 2024 18:42:55 -0400 Subject: [PATCH 11/14] adding client editing of values --- client/platform/web-girder/router.ts | 2 +- .../web-girder/views/DIVEMetadataEditKey.vue | 79 +++++++++++++++++++ .../web-girder/views/DIVEMetadataFilter.vue | 1 + .../web-girder/views/DIVEMetadataSearch.vue | 79 ++++++++++++------- 4 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 client/platform/web-girder/views/DIVEMetadataEditKey.vue diff --git a/client/platform/web-girder/router.ts b/client/platform/web-girder/router.ts index e646518..78dda48 100644 --- a/client/platform/web-girder/router.ts +++ b/client/platform/web-girder/router.ts @@ -12,7 +12,7 @@ import DataShared from './views/DataShared.vue'; import DataBrowser from './views/DataBrowser.vue'; import Summary from './views/Summary.vue'; import DIVEMetadataSearchVue from './views/DIVEMetadataSearch.vue'; -import DiveMetadataEditVue from './views/DiveMetadataEdit.vue'; +import DiveMetadataEditVue from './views/DIVEMetadataEdit.vue'; Vue.use(Router); diff --git a/client/platform/web-girder/views/DIVEMetadataEditKey.vue b/client/platform/web-girder/views/DIVEMetadataEditKey.vue new file mode 100644 index 0000000..e7ea95d --- /dev/null +++ b/client/platform/web-girder/views/DIVEMetadataEditKey.vue @@ -0,0 +1,79 @@ + + + diff --git a/client/platform/web-girder/views/DIVEMetadataFilter.vue b/client/platform/web-girder/views/DIVEMetadataFilter.vue index 7950149..29faf45 100644 --- a/client/platform/web-girder/views/DIVEMetadataFilter.vue +++ b/client/platform/web-girder/views/DIVEMetadataFilter.vue @@ -98,6 +98,7 @@ export default defineComponent({ const getFilters = async () => { const filterData = await getMetadataFilterValues(props.id); filters.value = filterData.data.metadataKeys; + emit('filterData', filters.value.data); }; onBeforeMount(async () => { await getFilters(); diff --git a/client/platform/web-girder/views/DIVEMetadataSearch.vue b/client/platform/web-girder/views/DIVEMetadataSearch.vue index dd42165..05f4c4e 100644 --- a/client/platform/web-girder/views/DIVEMetadataSearch.vue +++ b/client/platform/web-girder/views/DIVEMetadataSearch.vue @@ -4,17 +4,22 @@ import { } from 'vue'; import { DIVEMetadataResults, DIVEMetadataFilter, filterDiveMetadata, MetadataResultItem, FilterDisplayConfig, + DIVEMetadataFilterValueResults, + MetadataFilterKeysItem, + setDiveDatasetMetadataKey, } from 'platform/web-girder/api/divemetadata.service'; import { getFolder } from 'platform/web-girder/api/girder.service'; import { useGirderRest } from 'platform/web-girder/plugins/girder'; import DIVEMetadataFilterVue from './DIVEMetadataFilter.vue'; import DIVEMetadataCloneVue from './DIVEMetadataClone.vue'; +import DIVEMetadataEditKey from './DIVEMetadataEditKey.vue'; export default defineComponent({ name: 'DIVEMetadataSearch', components: { DIVEMetadataFilterVue, DIVEMetadataCloneVue, + DIVEMetadataEditKey, }, props: { id: { @@ -28,6 +33,7 @@ export default defineComponent({ }, setup(props) { const folderList: Ref = ref([]); + const unlockedMap: Ref> = ref({}); const displayConfig: Ref = ref({ display: [], hide: [], categoricalLimit: 50 }); const totalPages = ref(0); const currentPage = ref(0); @@ -79,7 +85,7 @@ export default defineComponent({ const storedSortVal = ref('filename'); const storedSortDir = ref(1); - const updateFilter = async ({ filter, sortVal, sortDir } : { filter?:DIVEMetadataFilter, sortVal?: string, sortDir?: number}) => { + const updateFilter = async ({ filter, sortVal, sortDir }: { filter?: DIVEMetadataFilter, sortVal?: string, sortDir?: number }) => { if (filter) { filters.value = filter; currentPage.value = 0; @@ -116,6 +122,21 @@ export default defineComponent({ return advancedList; }; const openClone = ref(false); + + const setFilterData = (data: DIVEMetadataFilterValueResults) => { + //get unlock fields and their data types: + const { unlocked } = data; + unlockedMap.value = {}; + unlocked.forEach((item) => { + if (data.metadataKeys[item]) { + unlockedMap.value[item] = data.metadataKeys[item]; + } + }); + }; + + const updateDiveMetadataKeyVal = async (key: string, val: boolean | number | string) => { + await setDiveDatasetMetadataKey(props.id, key, val); + }; return { totalPages, count, @@ -131,6 +152,9 @@ export default defineComponent({ //Cloning openClone, currentFilter, + setFilterData, + unlockedMap, + updateDiveMetadataKeyVal, }; }, }); @@ -148,34 +172,22 @@ export default defineComponent({ :display-config="displayConfig" @update:currentPage="changePage($event)" @updateFilters="updateFilter($event)" + @filter-data="setFilterData($event)" > - +
{{ item.filename }}
@@ -204,7 +212,15 @@ export default defineComponent({ {{ display }}: -
+
+ +
+
{{ item.metadata[display] }}
@@ -217,7 +233,15 @@ export default defineComponent({ {{ dataKey }}: -
+
+ +
+
{{ data }}
@@ -233,9 +257,10 @@ export default defineComponent({ From 46dc97f1bb454ee3ffaeb487f4d7575a605c500e Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Wed, 9 Oct 2024 13:57:45 -0400 Subject: [PATCH 12/14] adding in metadata editing system --- .../web-girder/api/divemetadata.service.ts | 19 +- .../platform/web-girder/api/girder.service.ts | 17 ++ .../web-girder/views/CreateDIVEMetadata.vue | 204 ++++++++++++++++++ ...eMetadataEdit.vue => DIVEMetadataEdit.vue} | 13 +- .../web-girder/views/DIVEMetadataEditKey.vue | 56 ++--- .../web-girder/views/DIVEMetadataFilter.vue | 3 +- .../web-girder/views/DIVEMetadataSearch.vue | 38 +++- client/platform/web-girder/views/Home.vue | 7 + docs/index.md | 2 +- server/dive_server/views_metadata.py | 56 +++-- server/dive_utils/metadata/models.py | 17 +- 11 files changed, 371 insertions(+), 61 deletions(-) create mode 100644 client/platform/web-girder/views/CreateDIVEMetadata.vue rename client/platform/web-girder/views/{DiveMetadataEdit.vue => DIVEMetadataEdit.vue} (96%) diff --git a/client/platform/web-girder/api/divemetadata.service.ts b/client/platform/web-girder/api/divemetadata.service.ts index 77389b5..bb056a3 100644 --- a/client/platform/web-girder/api/divemetadata.service.ts +++ b/client/platform/web-girder/api/divemetadata.service.ts @@ -75,6 +75,15 @@ function createDiveMetadataClone(folder: string, filters: DIVEMetadataFilter, de }); } +export interface createDiveMetadataResponse { + 'results': string, + 'errors': string[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + 'metadataKeys': any[]; + 'folderId': string; + +} + function createDiveMetadataFolder( parentFolder: string, name: string, @@ -87,7 +96,7 @@ function createDiveMetadataFolder( import: true, keys: ['width', 'height', 'display_aspect_ratio'], }, ) { - return girderRest.post(`dive_metadata/create_metadata_folder/${parentFolder}`, null, { + return girderRest.post(`dive_metadata/create_metadata_folder/${parentFolder}`, null, { params: { name, rootFolderId, categoricalLimit, displayConfig, ffprobeMetadata, }, @@ -102,7 +111,8 @@ function modifyDiveMetadataPermission(rootMetadataFolder: string, key: string, u }); } -function addDiveMetadataKey(rootMetadataFolder: string, key: string, category: 'numerical' | 'categorical' | 'search' | 'boolean', unlocked = false, values: string[] = [], defaultValue?: number | string | boolean) { +function addDiveMetadataKey(rootMetadataFolder: string, key: string, category: 'numerical' | 'categorical' | 'search' | 'boolean', unlocked = false, valueList: string[] = [], defaultValue?: number | string | boolean) { + const values = valueList.length ? valueList : undefined; return girderRest.put(`dive_metadata/${rootMetadataFolder}/add_key`, null, { params: { key, category, unlocked, values, default_value: defaultValue, @@ -121,8 +131,9 @@ function deleteDiveMetadataKey(rootMetadataFolder:string, key: string) { function deleteDiveDatasetMetadataKey(diveDatasetId: string, key: string) { return girderRest.delete(`dive_metadata/${diveDatasetId}`, { params: { key } }); } -function setDiveDatasetMetadataKey(diveDatasetId: string, key: string, value: number | string | boolean) { - return girderRest.patch(`dive_metadata/${diveDatasetId}`, { +function setDiveDatasetMetadataKey(diveDatasetId: string, key: string, updateValue?: number | string | boolean) { + const value = updateValue === undefined ? null : updateValue; + return girderRest.patch(`dive_metadata/${diveDatasetId}`, null, { params: { key, value, }, diff --git a/client/platform/web-girder/api/girder.service.ts b/client/platform/web-girder/api/girder.service.ts index cbd6418..9c3720b 100644 --- a/client/platform/web-girder/api/girder.service.ts +++ b/client/platform/web-girder/api/girder.service.ts @@ -27,6 +27,22 @@ function getFolder(folderId: string) { return girderRest.get(`folder/${folderId}`); } +export interface AccessType { + flags: string[]; + id: string; + level: number; + login: string; + name: string; +} +export interface FolderAccessType { + groups: AccessType[]; + users: AccessType[]; +} + +function getFolderAccess(folderId: string) { + return girderRest.get(`folder/${folderId}/access`); +} + function setUsePrivateQueue(userId: string, value = false) { return girderRest.put<{ user_private_queue_enabled: boolean; @@ -42,4 +58,5 @@ export { getItemsInFolder, getFolder, setUsePrivateQueue, + getFolderAccess, }; diff --git a/client/platform/web-girder/views/CreateDIVEMetadata.vue b/client/platform/web-girder/views/CreateDIVEMetadata.vue new file mode 100644 index 0000000..f3b5067 --- /dev/null +++ b/client/platform/web-girder/views/CreateDIVEMetadata.vue @@ -0,0 +1,204 @@ + + + diff --git a/client/platform/web-girder/views/DiveMetadataEdit.vue b/client/platform/web-girder/views/DIVEMetadataEdit.vue similarity index 96% rename from client/platform/web-girder/views/DiveMetadataEdit.vue rename to client/platform/web-girder/views/DIVEMetadataEdit.vue index 8f85390..673a30e 100644 --- a/client/platform/web-girder/views/DiveMetadataEdit.vue +++ b/client/platform/web-girder/views/DIVEMetadataEdit.vue @@ -13,6 +13,7 @@ import { } from 'platform/web-girder/api/divemetadata.service'; import { getFolder } from 'platform/web-girder/api/girder.service'; import { useGirderRest } from 'platform/web-girder/plugins/girder'; +import { useRouter } from 'vue-router/composables'; import DIVEMetadataFilterVue from './DIVEMetadataFilter.vue'; import DIVEMetadataCloneVue from './DIVEMetadataClone.vue'; @@ -43,6 +44,8 @@ export default defineComponent({ const displayConfig: Ref = ref({ display: [], hide: [], categoricalLimit: 50 }); const girderRest = useGirderRest(); + const router = useRouter(); + const isOwnerAdmin = ref(false); const unlocked: Ref = ref([]); const metadataKeys: Ref> = ref({}); @@ -194,6 +197,10 @@ export default defineComponent({ addKeyDialog.value = true; }; + const returnToMetadata = () => { + router.push({ name: 'metadata', params: { id: props.id } }); + }; + return { metadataHeader, formattedKeys, @@ -206,6 +213,7 @@ export default defineComponent({ cancelNewKey, saveNewKey, initializeNewKey, + returnToMetadata, }; }, }); @@ -213,7 +221,10 @@ export default defineComponent({