diff --git a/multinet/api/admin/__init__.py b/multinet/api/admin/__init__.py index 999a51f..b589086 100644 --- a/multinet/api/admin/__init__.py +++ b/multinet/api/admin/__init__.py @@ -1,4 +1,5 @@ +from .session import NetworkSessionAdmin, TableSessionAdmin from .upload import UploadAdmin from .workspace import WorkspaceAdmin -__all__ = ['UploadAdmin', 'WorkspaceAdmin'] +__all__ = ['UploadAdmin', 'WorkspaceAdmin', 'NetworkSessionAdmin', 'TableSessionAdmin'] diff --git a/multinet/api/admin/session.py b/multinet/api/admin/session.py new file mode 100644 index 0000000..3aa9117 --- /dev/null +++ b/multinet/api/admin/session.py @@ -0,0 +1,16 @@ +from django.contrib import admin +from guardian.admin import GuardedModelAdmin + +from multinet.api.models import NetworkSession, TableSession + + +@admin.register(NetworkSession) +class NetworkSessionAdmin(GuardedModelAdmin): + list_display = ['id', 'name', 'created', 'modified', 'starred', 'network'] + readonly_fields = ['id', 'created'] + + +@admin.register(TableSession) +class TableSessionAdmin(GuardedModelAdmin): + list_display = ['id', 'name', 'created', 'modified', 'starred'] + readonly_fields = ['id', 'created'] diff --git a/multinet/api/admin/workspace.py b/multinet/api/admin/workspace.py index c1a330c..29b941d 100644 --- a/multinet/api/admin/workspace.py +++ b/multinet/api/admin/workspace.py @@ -6,5 +6,5 @@ @admin.register(Workspace) class WorkspaceAdmin(GuardedModelAdmin): - list_display = ['id', 'name', 'arango_db_name', 'created', 'modified', 'public'] + list_display = ['id', 'name', 'arango_db_name', 'created', 'modified', 'public', 'starred'] readonly_fields = ['id', 'created'] diff --git a/multinet/api/migrations/0016_auto_20230809_1822.py b/multinet/api/migrations/0016_auto_20230809_1822.py new file mode 100644 index 0000000..21625fe --- /dev/null +++ b/multinet/api/migrations/0016_auto_20230809_1822.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.18 on 2023-08-09 18:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0015_auto_20230804_2251'), + ] + + operations = [ + migrations.AddField( + model_name='networksession', + name='starred', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='tablesession', + name='starred', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='workspace', + name='starred', + field=models.BooleanField(default=False), + ), + ] diff --git a/multinet/api/models/session.py b/multinet/api/models/session.py index 909d2a8..d90afa0 100644 --- a/multinet/api/models/session.py +++ b/multinet/api/models/session.py @@ -7,6 +7,7 @@ class Session(TimeStampedModel): name = models.CharField(max_length=300) + starred = models.BooleanField(default=False) visapp = models.CharField(max_length=64) state = models.JSONField() diff --git a/multinet/api/models/workspace.py b/multinet/api/models/workspace.py index 6e5fa3a..260c044 100644 --- a/multinet/api/models/workspace.py +++ b/multinet/api/models/workspace.py @@ -41,6 +41,7 @@ class Workspace(TimeStampedModel): name = models.CharField(max_length=300, unique=True) public = models.BooleanField(default=False) owner = models.ForeignKey(User, on_delete=models.CASCADE) + starred = models.BooleanField(default=False) # Max length of 34, since uuid hexes are 32, + 2 chars on the front arango_db_name = models.CharField( diff --git a/multinet/api/tests/fuzzy.py b/multinet/api/tests/fuzzy.py index e408521..9c67d64 100644 --- a/multinet/api/tests/fuzzy.py +++ b/multinet/api/tests/fuzzy.py @@ -44,6 +44,7 @@ def workspace_re(workspace: Workspace): 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': workspace.public, + 'starred': workspace.starred, } diff --git a/multinet/api/tests/test_network.py b/multinet/api/tests/test_network.py index 3ac0e0c..0fc60a5 100644 --- a/multinet/api/tests/test_network.py +++ b/multinet/api/tests/test_network.py @@ -131,6 +131,7 @@ def test_network_rest_create( 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': False, + 'starred': False, }, } @@ -187,6 +188,7 @@ def test_network_rest_retrieve( 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': False, + 'starred': False, }, } else: @@ -212,6 +214,7 @@ def test_network_rest_retrieve_public(public_workspace: Workspace, api_client: A 'modified': TIMESTAMP_RE, 'arango_db_name': public_workspace.arango_db_name, 'public': True, + 'starred': False, }, } diff --git a/multinet/api/tests/test_table.py b/multinet/api/tests/test_table.py index 04dac21..c4f460e 100644 --- a/multinet/api/tests/test_table.py +++ b/multinet/api/tests/test_table.py @@ -129,6 +129,7 @@ def test_table_rest_create( 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': False, + 'starred': False, }, } @@ -186,6 +187,7 @@ def test_table_rest_retrieve( 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': False, + 'starred': False, }, } else: @@ -212,6 +214,7 @@ def test_table_rest_retrieve_public( 'modified': TIMESTAMP_RE, 'arango_db_name': public_workspace.arango_db_name, 'public': True, + 'starred': False, }, } diff --git a/multinet/api/tests/test_workspace.py b/multinet/api/tests/test_workspace.py index d7fd9f0..45a25a7 100644 --- a/multinet/api/tests/test_workspace.py +++ b/multinet/api/tests/test_workspace.py @@ -182,6 +182,7 @@ def test_workspace_rest_retrieve( 'modified': TIMESTAMP_RE, 'arango_db_name': workspace.arango_db_name, 'public': False, + 'starred': False, } @@ -198,6 +199,7 @@ def test_workspace_rest_retrieve_public( 'modified': TIMESTAMP_RE, 'arango_db_name': public_workspace.arango_db_name, 'public': True, + 'starred': False, } diff --git a/multinet/api/views/serializers.py b/multinet/api/views/serializers.py index de8f92c..6f66ec5 100644 --- a/multinet/api/views/serializers.py +++ b/multinet/api/views/serializers.py @@ -58,9 +58,7 @@ class Meta: class WorkspaceSerializer(serializers.ModelSerializer): class Meta: model = Workspace - fields = WorkspaceCreateSerializer.Meta.fields + [ - 'arango_db_name', - ] + fields = WorkspaceCreateSerializer.Meta.fields + ['arango_db_name', 'starred'] read_only_fields = ['created'] @@ -239,16 +237,41 @@ class NetworkTablesSerializer(serializers.Serializer): type = serializers.ChoiceField(choices=['node', 'edge', 'all'], default='all', required=False) +class NetworkSessionCreateSerializer(serializers.ModelSerializer): + class Meta: + model = NetworkSession + # All fields expect for starred + fields = [ + 'name', + 'visapp', + 'state', + 'network', + ] + + class NetworkSessionSerializer(serializers.ModelSerializer): class Meta: model = NetworkSession - fields = '__all__' + fields = NetworkSessionCreateSerializer.Meta.fields + ['starred', 'id'] + read_only_fields = ['created'] + + +class TableSessionCreateSerializer(serializers.ModelSerializer): + class Meta: + model = TableSession + fields = [ + 'name', + 'visapp', + 'state', + 'table', + ] class TableSessionSerializer(serializers.ModelSerializer): class Meta: model = TableSession - fields = '__all__' + fields = TableSessionCreateSerializer.Meta.fields + ['starred', 'id'] + read_only_fields = ['created'] class UploadCreateSerializer(serializers.Serializer): diff --git a/multinet/api/views/session.py b/multinet/api/views/session.py index 51e75de..5fe2f33 100644 --- a/multinet/api/views/session.py +++ b/multinet/api/views/session.py @@ -1,4 +1,4 @@ -from django.http.response import Http404 +from django.http.response import Http404, HttpResponseForbidden from django.shortcuts import get_object_or_404 from drf_yasg.utils import swagger_auto_schema from rest_framework import serializers, status @@ -15,22 +15,12 @@ from ..auth.decorators import require_workspace_permission from ..models import NetworkSession, TableSession, Workspace, WorkspaceRoleChoice from .common import NetworkWorkspaceChildMixin, TableWorkspaceChildMixin -from .serializers import NetworkSessionSerializer, TableSessionSerializer - - -class SessionCreateSerializer(serializers.Serializer): - workspace = serializers.CharField() - network = serializers.CharField(required=False) - table = serializers.CharField(required=False) - - visapp = serializers.CharField() - name = serializers.CharField() - - def validate(self, data): - if not bool(data.get('network')) ^ bool(data.get('table')): - raise serializers.ValidationError('exactly one of `network` or `table` is required') - - return data +from .serializers import ( + NetworkSessionCreateSerializer, + NetworkSessionSerializer, + TableSessionCreateSerializer, + TableSessionSerializer, +) class SessionStatePatchSerializer(serializers.Serializer): @@ -59,6 +49,9 @@ def state(self, request, parent_lookup_workspace__name: str, pk=None): if workspace.id != session_ws.id: raise Http404 + if session.starred: + return HttpResponseForbidden('Starred session state cannot be modified') + serializer = SessionStatePatchSerializer(data=request.data) serializer.is_valid(raise_exception=True) data = serializer.validated_data['state'] @@ -95,7 +88,16 @@ class NetworkSessionViewSet(NetworkWorkspaceChildMixin, SessionViewSet): queryset = NetworkSession.objects.all().select_related('network__workspace') serializer_class = NetworkSessionSerializer + def get_serializer_class(self): + if self.action == 'create': + return NetworkSessionCreateSerializer + return NetworkSessionSerializer + class TableSessionViewSet(TableWorkspaceChildMixin, SessionViewSet): queryset = TableSession.objects.all().select_related('table__workspace') - serializer_class = TableSessionSerializer + + def get_serializer_class(self): + if self.action == 'create': + return TableSessionCreateSerializer + return TableSessionSerializer