Skip to content

Workspace fork #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions multinet/api/models/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,24 @@ def create_with_edge_definition(
workspace=workspace,
)

def copy(self, new_workspace: Workspace) -> Network:
"""
Copy the network to a new workspace.

This function creates a new network in the provided workspace, with the same
name and schema as this table. Permissions are wiped, the requester is the owner
"""
# Create a new table in the new workspace
new_network = Network.create_with_edge_definition(
name=self.name,
workspace=new_workspace,
edge_table=self.edge_tables()[0],
node_tables=self.node_tables(),
)

new_network.save()
return new_network

def __str__(self) -> str:
return self.name

Expand Down
23 changes: 23 additions & 0 deletions multinet/api/models/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,29 @@ def find_referenced_node_tables(self) -> Optional[Dict[str, Set[str]]]:

return referenced

def copy(self, new_workspace: Workspace) -> Table:
"""
Copy this table to a new workspace.

This function creates a new table in the provided workspace, with the same
name and schema as this table. Permissions are wiped, the requester is the owner
"""
# Create a new table in the new workspace
new_table = Table.objects.create(
name=self.name,
edge=self.edge,
workspace=new_workspace,
)

# Copy the data over
rows = self.get_rows()
for row in rows:
new_table.put_rows([row])

new_table.save()

return new_table

def __str__(self) -> str:
return self.name

Expand Down
55 changes: 53 additions & 2 deletions multinet/api/views/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from multinet.api.auth.decorators import require_workspace_ownership, require_workspace_permission
from multinet.api.models import Workspace, WorkspaceRole, WorkspaceRoleChoice
from multinet.api.auth.decorators import (
require_workspace_ownership,
require_workspace_permission,
)
from multinet.api.models import (
Network,
Table,
TableTypeAnnotation,
Workspace,
WorkspaceRole,
WorkspaceRoleChoice,
)
from multinet.api.utils.arango import ArangoQuery
from multinet.api.views.serializers import (
AqlQuerySerializer,
Expand Down Expand Up @@ -93,6 +103,47 @@ def create(self, request):
workspace.save()
return Response(WorkspaceSerializer(workspace).data, status=status.HTTP_200_OK)

@swagger_auto_schema(
responses={200: WorkspaceSerializer()},
)
@action(detail=True, url_path='fork', methods=['POST'])
def fork(self, request, name) -> Workspace:
"""
Fork this workspace, creating a new workspace with the same tables and networks.

The new workspace will be private by default and the name will be:
'Fork of {original workspace name}'
"""
workspace: Workspace = get_object_or_404(Workspace, name=name)

new_name = f'Fork of {workspace.name}'
# if the new name is not unique, append a number to the end
i = 1
while Workspace.objects.filter(name=new_name).exists():
new_name = f'Fork of {workspace.name} ({i})'
i += 1

new_workspace = Workspace.objects.create(name=new_name, owner=request.user, public=False)

# Copy the tables and permissions from the original workspace
for table in Table.objects.filter(workspace=workspace):
new_table = table.copy(new_workspace)
# Copy the type annotations
for type_annotation in TableTypeAnnotation.objects.filter(table=new_table):
TableTypeAnnotation.objects.create(
table=new_table, type=type_annotation.type, column=type_annotation.column
)
new_table.save()

# Copy the networks and their permissions from the original workspace
for network in Network.objects.filter(workspace=workspace):
new_network = network.copy(new_workspace)
new_network.save()

new_workspace.save()

return Response(WorkspaceSerializer(new_workspace).data, status=status.HTTP_200_OK)

@swagger_auto_schema(
request_body=WorkspaceRenameSerializer(),
responses={200: WorkspaceSerializer()},
Expand Down