def _update(Kind, table, nodes, now, user): if not nodes: return # 0: id # 1: X # 2: Y # 3: Z can_edit_all_or_fail(user, (node[0] for node in nodes.itervalues()), table) for node in nodes.itervalues(): Kind.objects.filter(id=int(node[0])).update(editor=user, edition_time=now, location_x=float(node[1]), location_y=float(node[2]), location_z=float(node[3]))
def _update(Kind, table, nodes, now, user): if not nodes: return # 0: id # 1: X # 2: Y # 3: Z can_edit_all_or_fail(user, (node[0] for node in nodes.itervalues()), table) for node in nodes.itervalues(): Kind.objects.filter(id=int(node[0])).update( editor=user, edition_time=now, location_x=float(node[1]), location_y=float(node[2]), location_z=float(node[3]))
def _update_location(table, nodes, now, user, cursor): if not nodes: return # 0: id # 1: X # 2: Y # 3: Z can_edit_all_or_fail(user, (node[0] for node in nodes), table) # Sanitize node details nodes = [(int(i), float(x), float(y), float(z)) for i, x, y, z in nodes] node_template = "(" + "),(".join(["%s, %s, %s, %s"] * len(nodes)) + ")" node_table = [v for k in nodes for v in k] cursor.execute( """ UPDATE location n SET editor_id = %s, location_x = target.x, location_y = target.y, location_z = target.z FROM (SELECT x.id, x.location_x AS old_loc_x, x.location_y AS old_loc_y, x.location_z AS old_loc_z, y.new_loc_x AS x, y.new_loc_y AS y, y.new_loc_z AS z FROM location x INNER JOIN (VALUES {}) y(id, new_loc_x, new_loc_y, new_loc_z) ON x.id = y.id FOR NO KEY UPDATE) target WHERE n.id = target.id RETURNING n.id, n.edition_time, target.old_loc_x, target.old_loc_y, target.old_loc_z """.format(node_template), [user.id] + node_table) updated_rows = cursor.fetchall() if len(nodes) != len(updated_rows): raise ValueError('Coudn\'t update node ' + ','.join( frozenset([str(r[0]) for r in nodes]) - frozenset([str(r[0]) for r in updated_rows]))) return updated_rows
def _update_location(table, nodes, now, user, cursor): if not nodes: return # 0: id # 1: X # 2: Y # 3: Z can_edit_all_or_fail(user, (node[0] for node in nodes), table) # Sanitize node details nodes = [(int(i), float(x), float(y), float(z)) for i,x,y,z in nodes] node_template = "(" + "),(".join(["%s, %s, %s, %s"] * len(nodes)) + ")" node_table = [v for k in nodes for v in k] cursor.execute(""" UPDATE location n SET editor_id = %s, location_x = target.x, location_y = target.y, location_z = target.z FROM (SELECT x.id, x.location_x AS old_loc_x, x.location_y AS old_loc_y, x.location_z AS old_loc_z, y.new_loc_x AS x, y.new_loc_y AS y, y.new_loc_z AS z FROM location x INNER JOIN (VALUES {}) y(id, new_loc_x, new_loc_y, new_loc_z) ON x.id = y.id FOR NO KEY UPDATE) target WHERE n.id = target.id RETURNING n.id, n.edition_time, target.old_loc_x, target.old_loc_y, target.old_loc_z """.format(node_template), [user.id] + node_table) updated_rows = cursor.fetchall() if len(nodes) != len(updated_rows): raise ValueError('Coudn\'t update node ' + ','.join(frozenset([str(r[0]) for r in nodes]) - frozenset([str(r[0]) for r in updated_rows]))) return updated_rows
def delete_neuron(request, project_id=None, neuron_id=None): """ Deletes a neuron if and only if two things are the case: 1. The user ownes all treenodes of the skeleton modeling the neuron in question and 2. The neuron is not annotated by other users. """ # Make sure the user can edit the neuron in general can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron') # Create class and relation dictionaries classes = dict( Class.objects.filter(project_id=project_id).values_list( 'class_name', 'id')) relations = dict( Relation.objects.filter(project_id=project_id).values_list( 'relation_name', 'id')) # Make sure the user has permission to edit all treenodes of all skeletons skeleton_ids = ClassInstanceClassInstance.objects.filter( class_instance_b=neuron_id, relation_id=relations['model_of']).values_list('class_instance_a', flat=True) for skid in skeleton_ids: others_nodes = Treenode.objects.filter(skeleton_id=skid).exclude( user_id=request.user.id).values_list('id', flat=True) if others_nodes: try: can_edit_all_or_fail(request.user, others_nodes, 'treenode') except Exception: raise Exception("You don't have permission to remove all " \ "treenodes of skeleton %s modeling this neuron. The " \ "neuron won't be deleted." % skid) # Make sure the user has permission to edit all annotations of this neuron annotation_ids = set( ClassInstanceClassInstance.objects.filter( class_instance_a_id=neuron_id, relation_id=relations['annotated_with']).values_list('id', flat=True)) if annotation_ids: try: can_edit_all_or_fail(request.user, annotation_ids, 'class_instance_class_instance') except Exception: raise Exception("You don't have permission to remove all " \ "annotations linked to this neuron. The neuron won't " \ "be deleted.") # Try to get the root node to have a valid location for a log entry if skeleton_ids: try: root_node = Treenode.objects.get(skeleton_id=skeleton_ids[0], parent=None) root_location = (root_node.location_x, root_node.location_y, root_node.location_z) except (Treenode.DoesNotExist, Treenode.MultipleObjectsReturned): root_location = None else: root_location = None # Delete neuron (and implicitely all annotation links due to Django's # cascading deletion) neuron = get_object_or_404(ClassInstance, pk=neuron_id) neuron.delete() # Delete all annotations that are not used anymore used_annotation_ids = set( ClassInstanceClassInstance.objects.filter( class_instance_b_id__in=annotation_ids, relation_id=relations['annotated_with']).values_list('id', flat=True)) unused_annotation_ids = annotation_ids.difference(used_annotation_ids) ClassInstance.objects.filter(id__in=unused_annotation_ids).delete() # Delete the skeletons (and their treenodes through cascading delete) cursor = connection.cursor() for skid in skeleton_ids: # Because there are constraints used in the database that Django is not # aware of, it's emulation of cascading deletion doesn't work. # Therefore, raw SQL needs to be used to use true cascading deletion. cursor.execute( ''' BEGIN; DELETE FROM change_request WHERE treenode_id IN ( SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s); DELETE FROM change_request WHERE connector_id IN ( SELECT id FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s); DELETE FROM treenode_class_instance WHERE treenode_id IN ( SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s); DELETE FROM treenode WHERE skeleton_id=%s AND project_id=%s; DELETE FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s; DELETE FROM class_instance WHERE id=%s AND project_id=%s; DELETE FROM review WHERE skeleton_id=%s AND project_id=%s; COMMIT; ''', (skid, project_id) * 7) # Insert log entry and refer to position of the first skeleton's root node insert_into_log( project_id, request.user.id, 'remove_neuron', root_location, 'Deleted neuron %s and skeleton(s) %s.' % (neuron_id, ', '.join([str(s) for s in skeleton_ids]))) return HttpResponse(json.dumps({ 'skeleton_ids': list(skeleton_ids), 'success': "Deleted neuron #%s as well as its skeletons and " \ "annotations." % neuron_id}))
def delete_neuron(request, project_id=None, neuron_id=None): """ Deletes a neuron if and only if two things are the case: 1. The user ownes all treenodes of the skeleton modeling the neuron in question and 2. The neuron is not annotated by other users. """ # Make sure the user can edit the neuron in general can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron') # Create class and relation dictionaries classes = dict(Class.objects.filter( project_id=project_id).values_list('class_name', 'id')) relations = dict(Relation.objects.filter( project_id=project_id).values_list('relation_name', 'id')) # Make sure the user has permission to edit all treenodes of all skeletons skeleton_ids = ClassInstanceClassInstance.objects.filter( class_instance_b=neuron_id, relation_id=relations['model_of']).values_list( 'class_instance_a', flat=True) for skid in skeleton_ids: others_nodes = Treenode.objects.filter(skeleton_id=skid).exclude( user_id=request.user.id).values_list('id', flat=True) if others_nodes: try: can_edit_all_or_fail(request.user, others_nodes, 'treenode') except Exception: raise Exception("You don't have permission to remove all " \ "treenodes of skeleton %s modeling this neuron. The " \ "neuron won't be deleted." % skid) # Make sure the user has permission to edit all annotations of this neuron annotation_ids = set(ClassInstanceClassInstance.objects.filter( class_instance_a_id=neuron_id, relation_id=relations['annotated_with']).values_list( 'id', flat=True)) if annotation_ids: try: can_edit_all_or_fail(request.user, annotation_ids, 'class_instance_class_instance') except Exception: raise Exception("You don't have permission to remove all " \ "annotations linked to this neuron. The neuron won't " \ "be deleted.") # Try to get the root node to have a valid location for a log entry if skeleton_ids: try: root_node = Treenode.objects.get( skeleton_id=skeleton_ids[0], parent=None) root_location = (root_node.location_x, root_node.location_y, root_node.location_z) except (Treenode.DoesNotExist, Treenode.MultipleObjectsReturned): root_location = None else: root_location = None # Delete neuron (and implicitely all annotation links due to Django's # cascading deletion) neuron = get_object_or_404(ClassInstance, pk=neuron_id) neuron.delete() # Delete all annotations that are not used anymore used_annotation_ids = set(ClassInstanceClassInstance.objects.filter( class_instance_b_id__in=annotation_ids, relation_id=relations['annotated_with']).values_list( 'id', flat=True)) unused_annotation_ids = annotation_ids.difference(used_annotation_ids) ClassInstance.objects.filter(id__in=unused_annotation_ids).delete() # Delete the skeletons (and their treenodes through cascading delete) cursor = connection.cursor() for skid in skeleton_ids: # Because there are constraints used in the database that Django is not # aware of, it's emulation of cascading deletion doesn't work. # Therefore, raw SQL needs to be used to use true cascading deletion. cursor.execute(''' BEGIN; DELETE FROM change_request WHERE treenode_id IN ( SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s); DELETE FROM change_request WHERE connector_id IN ( SELECT id FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s); DELETE FROM treenode_class_instance WHERE treenode_id IN ( SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s); DELETE FROM treenode WHERE skeleton_id=%s AND project_id=%s; DELETE FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s; DELETE FROM class_instance WHERE id=%s AND project_id=%s; DELETE FROM review WHERE skeleton_id=%s AND project_id=%s; COMMIT; ''', (skid, project_id) * 7) # Insert log entry and refer to position of the first skeleton's root node insert_into_log(project_id, request.user.id, 'remove_neuron', root_location, 'Deleted neuron %s and skeleton(s) %s.' % (neuron_id, ', '.join([str(s) for s in skeleton_ids]))) return HttpResponse(json.dumps({ 'skeleton_ids': list(skeleton_ids), 'success': "Deleted neuron #%s as well as its skeletons and " \ "annotations." % neuron_id}))
def rename_neurons(request: HttpRequest, project_id=None) -> JsonResponse: """Rename a neuron. If a neuron is not locked by a user on which the current user has no permission, the name of neuron can be changed through this endpoint. Neuron names are currently not allowed to contain pipe characters ("|"). --- parameters: - name: neuron_names description: A list of two-element tuples, containing of neuron ID and new name each. required: true type: array paramType: form type: success: description: If renaming was successful type: boolean required: true renamed_neurons: description: IDs of the renamed neurons type: integer required: true old_names: description: Old names of the renamed neurons type: string required: true """ neuron_name_pairs = get_request_list(request.POST, 'names') if not neuron_name_pairs: raise ValueError('Need at least one neuron ID / name mapping') neuron_ids = [int(k) for k, _ in neuron_name_pairs] neuron_names = [v for _, v in neuron_name_pairs] # Make sure the user can edit the neuron can_edit_all_or_fail(request.user, neuron_ids, 'class_instance') # Do not allow '|' in name because it is used as string separator in NeuroHDF export for new_name in neuron_names: if '|' in new_name: raise ValueError('New name should not contain pipe character') cursor = connection.cursor() cursor.execute( """ UPDATE class_instance ci SET name = new_data.name FROM UNNEST( %(neuron_ids)s::bigint[], %(neuron_names)s::text[]) new_data(id, name) JOIN class_instance ci_old ON ci_old.id = new_data.id WHERE ci.id = new_data.id AND ci.project_id = %(project_id)s RETURNING ci.id, ci_old.name, ci.name """, { 'project_id': project_id, 'neuron_ids': neuron_ids, 'neuron_names': neuron_names, }) rows = cursor.fetchall() updated_ids = [r[0] for r in rows] old_names = [r[1] for r in rows] new_names = [r[2] for r in rows] # Insert log entry and return successfully if len(updated_ids) == 1: insert_into_log( project_id, request.user.id, "rename_neuron", None, f"Renamed neuron with ID {neuron_ids[0]} from {old_names[0]} to {neuron_names[0]}" ) else: insert_into_log( project_id, request.user.id, "rename_neuron", None, f"Renamed {len(neuron_ids)} neurons. IDs: {', '.join(str(uid) for uid in updated_ids)} Old names: {', '.join(old_names)} New names: {', '.join(new_names)}" ) return JsonResponse({ 'success': True, 'renamed_neurons': updated_ids, 'old_names': old_names })