Skip to content

Commit

Permalink
Add dataset ID to history entries
Browse files Browse the repository at this point in the history
Fix some minor bugs around history
  • Loading branch information
bruyeret committed Jul 5, 2023
1 parent 2ed81bf commit 57419c5
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,49 @@
from girder.api.rest import Resource, loadmodel
from girder.constants import AccessType
from girder.exceptions import AccessException, RestException
from ..helpers.proxiedModel import recordable
from ..helpers.proxiedModel import recordable, cacheBodyJson
from ..models.annotation import Annotation as AnnotationModel

from bson.objectid import ObjectId


# Helper functions to get dataset ID for recordable endpoints

def getDatasetIdFromAnnotationInBody(self: 'Annotation', *args, **kwargs):
annotation = self.getBodyJson()
return annotation['datasetId']

def getDatasetIdFromAnnotationListInBody(self: 'Annotation', *args, **kwargs):
annotations = self.getBodyJson()
if len(annotations) <= 0:
return None
else:
return annotations[0]['datasetId']

def getDatasetIdFromLoadedAnnotation(self: 'Annotation', *args, **kwargs):
annotation = kwargs['upenn_annotation']
return annotation['datasetId']

def getDatasetIdFromAnnotationIdListInBody(self: 'Annotation', *args, **kwargs):
annotationStringIds = self.getBodyJson()
query = {
'_id': { '$in': [ObjectId(stringId) for stringId in annotationStringIds] },
}
cursor = self._annotationModel.findWithPermissions(query, user=self.getCurrentUser(), level=AccessType.READ, limit=1)
annotation = next(cursor, None)
if annotation is None:
return None
else:
return annotation['datasetId']


class Annotation(Resource):

def __init__(self):
super().__init__()
self.resourceName = 'upenn_annotation'

self._annotationModel = AnnotationModel()
self._annotationModel: AnnotationModel = AnnotationModel()

self.route('DELETE', (':id',), self.delete)
self.route('GET', (':id',), self.get)
Expand All @@ -31,18 +63,20 @@ def __init__(self):
# TODO(performance): use objectId whenever possible
# TODO: error handling and documentation

@cacheBodyJson
@access.user
@describeRoute(Description("Create a new annotation").param('body', 'Annotation Object', paramType='body'))
@recordable('Create an annotation')
@recordable('Create an annotation', getDatasetIdFromAnnotationInBody)
def create(self, params):
currentUser = self.getCurrentUser()
if not currentUser:
raise AccessException('User not found', 'currentUser')
return self._annotationModel.create(currentUser, self.getBodyJson())

@cacheBodyJson
@access.user
@describeRoute(Description("Create multiple new annotations").param('body', 'Annotation Object List', paramType='body'))
@recordable('Create multiple annotations')
@recordable('Create multiple annotations', getDatasetIdFromAnnotationListInBody)
def createMultiple(self, params):
currentUser = self.getCurrentUser()
if not currentUser:
Expand All @@ -53,14 +87,15 @@ def createMultiple(self, params):
.errorResponse('Write access was denied for the annotation.', 403))
@access.user
@loadmodel(model='upenn_annotation', plugin='upenncontrast_annotation', level=AccessType.WRITE)
@recordable('Delete an annotation')
@recordable('Delete an annotation', getDatasetIdFromLoadedAnnotation)
def delete(self, upenn_annotation, params):
self._annotationModel.delete(upenn_annotation)

@cacheBodyJson
@access.user
@describeRoute(Description("Delete all annotations in the id list")
.param('body', 'A list of all annotation ids to delete.', paramType='body'))
@recordable('Delete multiple annotations')
@recordable('Delete multiple annotations', getDatasetIdFromAnnotationIdListInBody)
def deleteMultiple(self, params):
stringIds = [stringId for stringId in self.getBodyJson()]
self._annotationModel.deleteMultiple(stringIds)
Expand All @@ -74,7 +109,7 @@ def deleteMultiple(self, params):
.errorResponse("Validation Error: JSON doesn't follow schema."))
@access.user
@loadmodel(model='upenn_annotation', plugin='upenncontrast_annotation', level=AccessType.WRITE)
@recordable('Update an annotation')
@recordable('Update an annotation', getDatasetIdFromLoadedAnnotation)
def update(self, upenn_annotation, params):
upenn_annotation.update(self.getBodyJson())
self._annotationModel.update(upenn_annotation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,52 @@
from girder.api.rest import Resource, loadmodel
from girder.constants import AccessType
from girder.exceptions import AccessException
from ..helpers.proxiedModel import recordable
from ..helpers.proxiedModel import recordable, cacheBodyJson
from ..models.connections import AnnotationConnection as ConnectionModel
from ..models.annotation import Annotation as AnnotationModel

from bson.objectid import ObjectId


# Helper functions to get dataset ID for recordable endpoints

def getDatasetIdFromConnectionInBody(self: 'AnnotationConnection', *args, **kwargs):
connection = self.getBodyJson()
return connection['datasetId']

def getDatasetIdFromConnectionListInBody(self: 'AnnotationConnection', *args, **kwargs):
connections = self.getBodyJson()
if len(connections) <= 0:
return None
else:
return connections[0]['datasetId']

def getDatasetIdFromLoadedConnection(self: 'AnnotationConnection', *args, **kwargs):
connection = kwargs['annotation_connection']
return connection['datasetId']

def getDatasetIdFromConnectionIdListInBody(self: 'AnnotationConnection', *args, **kwargs):
connectionStringIds = self.getBodyJson()
query = {
'_id': { '$in': [ObjectId(stringId) for stringId in connectionStringIds] },
}
cursor = self._connectionModel.findWithPermissions(query, user=self.getCurrentUser(), level=AccessType.READ, limit=1)
connection = next(cursor, None)
if connection is None:
return None
else:
return connection['datasetId']

def getDatasetIdFromInfoInBody(self: 'AnnotationConnection', *args, **kwargs):
info = self.getBodyJson()
annotationsIdsToConnect = info['annotationsIds']
annotationModel: AnnotationModel = AnnotationModel()
for stringId in annotationsIdsToConnect:
id = ObjectId(stringId)
annotation = annotationModel.getAnnotationById(id, self.getCurrentUser())
if annotation is not None:
return annotation['datasetId']
return None


class AnnotationConnection(Resource):
Expand All @@ -13,7 +57,7 @@ def __init__(self):
super().__init__()
self.resourceName = 'annotation_connection'

self._connectionModel = ConnectionModel()
self._connectionModel: ConnectionModel = ConnectionModel()

self.route('DELETE', (':id',), self.delete)
self.route('GET', (':id',), self.get)
Expand All @@ -29,18 +73,20 @@ def __init__(self):
# TODO: creation date, update date, creatorId
# TODO: error handling and documentation

@cacheBodyJson
@access.user
@describeRoute(Description("Create a new connection").param('body', 'Connection Object', paramType='body'))
@recordable('Create a connection')
@recordable('Create a connection', getDatasetIdFromConnectionInBody)
def create(self, params):
currentUser = self.getCurrentUser()
if not currentUser:
raise AccessException('User not found', 'currentUser')
return self._connectionModel.create(currentUser, self.getBodyJson())

@cacheBodyJson
@access.user
@describeRoute(Description("Create multiple new connections").param('body', 'Connection Object List', paramType='body'))
@recordable('Create multiple connections')
@recordable('Create multiple connections', getDatasetIdFromConnectionListInBody)
def multipleCreate(self, params):
currentUser = self.getCurrentUser()
if not currentUser:
Expand All @@ -51,14 +97,15 @@ def multipleCreate(self, params):
.errorResponse('Write access was denied for the connection.', 403))
@access.user
@loadmodel(model='annotation_connection', plugin='upenncontrast_annotation', level=AccessType.WRITE)
@recordable('Delete a connection')
@recordable('Delete a connection', getDatasetIdFromLoadedConnection)
def delete(self, annotation_connection, params):
self._connectionModel.remove(annotation_connection)


@cacheBodyJson
@access.user
@describeRoute(Description("Delete all annotation connections in the id list")
.param('body', 'A list of all annotation connection ids to delete.', paramType='body'))
@recordable('Delete multiple connections')
@recordable('Delete multiple connections', getDatasetIdFromConnectionIdListInBody)
def deleteMultiple(self, params):
stringIds = [stringId for stringId in self.getBodyJson()]
self._connectionModel.deleteMultiple(stringIds)
Expand All @@ -72,7 +119,7 @@ def deleteMultiple(self, params):
.errorResponse("Validation Error: JSON doesn't follow schema."))
@access.user
@loadmodel(model='annotation_connection', plugin='upenncontrast_annotation', level=AccessType.WRITE)
@recordable('Update a connection')
@recordable('Update a connection', getDatasetIdFromLoadedConnection)
def update(self, connection, params):
connection.update(self.getBodyJson())
self._connectionModel.update(connection)
Expand Down Expand Up @@ -115,9 +162,10 @@ def get(self, annotation_connection):
return annotation_connection


@cacheBodyJson
@access.user
@describeRoute(Description("Create connections between annotations").param('body', 'Connection Object', paramType='body'))
@recordable('Create connections with nearest')
@recordable('Create connections with nearest', getDatasetIdFromInfoInBody)
def connectToNearest(self, params):
currentUser = self.getCurrentUser()
if not currentUser:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
from girder.api import access
from girder.api.describe import Description, describeRoute
from girder.api.rest import Resource
from girder.constants import SortDir, AccessType
from girder.exceptions import AccessException
from girder.exceptions import AccessException, RestException
from ..models.history import History as HistoryModel
from bson.objectid import ObjectId

class History(Resource):

def __init__(self):
super().__init__()
self.resourceName = 'history'

self._historyModel = HistoryModel()
self._historyModel: HistoryModel = HistoryModel()

self.route('GET', (), self.find)
self.route('PUT', ('undo',), self.undo)
self.route('PUT', ('redo',), self.redo)

@access.user
@describeRoute(Description("Get last history actions for the user, from the most recent to the oldest"))
@describeRoute(Description("Get last history actions for the user, from the most recent to the oldest")
.param('datasetId', 'The dataset in which are the history entries', required=True))
def find(self, params):
user = self.getCurrentUser()
if user is None:
raise AccessException('You must be logged in.')
return self._historyModel.getLastEntries(user)
if 'datasetId' not in params:
raise RestException(code=400, message='Dataset ID is missing')
datasetId = ObjectId(params['datasetId'])
return self._historyModel.getLastEntries(user, datasetId)

@access.user
@describeRoute(Description("Undo the last history entry which hasn't been undone"))
@describeRoute(Description("Undo the last history entry which hasn't been undone")
.param('datasetId', 'The dataset in which undo should be done', required=True))
def undo(self, params):
user = self.getCurrentUser()
if user is None:
raise AccessException('You must be logged in.')
return self._historyModel.undo(user)
if 'datasetId' not in params:
raise RestException(code=400, message='Dataset ID is missing')
datasetId = ObjectId(params['datasetId'])
return self._historyModel.undo(user, datasetId)

@access.user
@describeRoute(Description("Redo the last history entry which has been undone"))
@describeRoute(Description("Redo the last history entry which has been undone")
.param('datasetId', 'The dataset in which redo should be done', required=True))
def redo(self, params):
user = self.getCurrentUser()
if user is None:
raise AccessException('You must be logged in.')
self._historyModel.redo(user)
if 'datasetId' not in params:
raise RestException(code=400, message='Dataset ID is missing')
datasetId = ObjectId(params['datasetId'])
self._historyModel.redo(user, datasetId)
Loading

0 comments on commit 57419c5

Please sign in to comment.