Skip to content

Commit

Permalink
proposed server changes
Browse files Browse the repository at this point in the history
  • Loading branch information
hasan7n committed Nov 19, 2023
1 parent 485e3e0 commit 26507cc
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 71 deletions.
27 changes: 27 additions & 0 deletions server/benchmark/permissions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from rest_framework.permissions import BasePermission
from .models import Benchmark
from benchmarkdataset.models import BenchmarkDataset
from django.db.models import OuterRef, Subquery


class IsAdmin(BasePermission):
Expand All @@ -25,3 +27,28 @@ def has_permission(self, request, view):
return True
else:
return False


class IsAssociatedDatasetOwner(BasePermission):
def has_permission(self, request, view):
pk = view.kwargs.get("pk", None)
if not pk:
return False

latest_datasets_assocs_status = (
BenchmarkDataset.objects.all()
.filter(benchmark__id=pk, dataset__id=OuterRef("id"))
.order_by("-created_at")[:1]
.values("approval_status")
)

user_associated_datasets = (
request.user.dataset_set.all()
.annotate(assoc_status=Subquery(latest_datasets_assocs_status))
.filter(assoc_status="APPROVED")
)

if user_associated_datasets.exists():
return True
else:
return False
20 changes: 10 additions & 10 deletions server/benchmark/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from mlcube.serializers import MlCubeSerializer
from dataset.serializers import DatasetSerializer
from benchmarkmodel.serializers import BenchmarkListofModelsSerializer
from benchmarkdataset.serializers import BenchmarkListofDatasetsSerializer
from result.serializers import ModelResultSerializer
from django.http import Http404
from rest_framework.generics import GenericAPIView
Expand Down Expand Up @@ -40,7 +40,8 @@ def post(self, request, format=None):


class BenchmarkModelList(GenericAPIView):
serializer_class = MlCubeSerializer
# TODO: fix permissions?
serializer_class = BenchmarkListofModelsSerializer
queryset = ""

def get_object(self, pk):
Expand All @@ -54,15 +55,15 @@ def get(self, request, pk, format=None):
Retrieve models associated with a benchmark instance.
"""
benchmark = self.get_object(pk)
modelgroups = benchmark.benchmarkmodel_set.all()
models = [gp.model_mlcube for gp in modelgroups]
models = benchmark.benchmarkmodel_set.all()
models = self.paginate_queryset(models)
serializer = MlCubeSerializer(models, many=True)
serializer = BenchmarkListofModelsSerializer(models, many=True)
return self.get_paginated_response(serializer.data)


class BenchmarkDatasetList(GenericAPIView):
serializer_class = DatasetSerializer
permission_classes = [IsAdmin | IsBenchmarkOwner]
serializer_class = BenchmarkListofDatasetsSerializer
queryset = ""

def get_object(self, pk):
Expand All @@ -76,10 +77,9 @@ def get(self, request, pk, format=None):
Retrieve datasets associated with a benchmark instance.
"""
benchmark = self.get_object(pk)
datasetgroups = benchmark.benchmarkdataset_set.all()
datasets = [gp.dataset for gp in datasetgroups]
datasets = benchmark.benchmarkdataset_set.all()
datasets = self.paginate_queryset(datasets)
serializer = DatasetSerializer(datasets, many=True)
serializer = BenchmarkListofDatasetsSerializer(datasets, many=True)
return self.get_paginated_response(serializer.data)


Expand Down
28 changes: 19 additions & 9 deletions server/benchmarkdataset/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Meta:
def validate(self, data):
# TODO: there is no check if the dataset was prepared with the data
# prep mlcube of the benchmark
# TODO: define what should happen to existing assets when an association
# is rejected after being approved (results)

bid = self.context["request"].data.get("benchmark")
dataset = self.context["request"].data.get("dataset")
approval_status = self.context["request"].data.get("approval_status", "PENDING")
Expand Down Expand Up @@ -91,27 +94,34 @@ class Meta:
def validate(self, data):
if not self.instance:
raise serializers.ValidationError("No dataset association found")
return data

def validate_approval_status(self, cur_approval_status):
last_approval_status = self.instance.approval_status
cur_approval_status = data["approval_status"]
if last_approval_status != "PENDING":
raise serializers.ValidationError(
"User can approve or reject only a pending request"
)
initiated_user = self.instance.initiated_by
current_user = self.context["request"].user
if (
last_approval_status != cur_approval_status
and cur_approval_status == "APPROVED"
):
if cur_approval_status == "APPROVED":
if current_user.id == initiated_user.id:
raise serializers.ValidationError(
"Same user cannot approve the association request"
)
return data
return cur_approval_status

def update(self, instance, validated_data):
instance.approval_status = validated_data["approval_status"]
if instance.approval_status != "PENDING":
instance.approved_at = timezone.now()
# TODO: check if approved_at will change always
if "approval_status" in validated_data:
instance.approval_status = validated_data["approval_status"]
if instance.approval_status != "PENDING":
instance.approved_at = timezone.now()
instance.save()
return instance


class BenchmarkListofDatasetsSerializer(serializers.ModelSerializer):
class Meta:
model = BenchmarkDataset
fields = ["dataset", "approval_status", "created_at"]
13 changes: 9 additions & 4 deletions server/benchmarkmodel/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class Meta:
fields = "__all__"

def validate(self, data):
# TODO: define what should happen to existing assets when an association
# is rejected after being approved (results)
bid = self.context["request"].data.get("benchmark")
mlcube = self.context["request"].data.get("model_mlcube")
approval_status = self.context["request"].data.get("approval_status", "PENDING")
Expand Down Expand Up @@ -101,10 +103,7 @@ def validate_approval_status(self, cur_approval_status):
)
initiated_user = self.instance.initiated_by
current_user = self.context["request"].user
if (
last_approval_status != cur_approval_status
and cur_approval_status == "APPROVED"
):
if cur_approval_status == "APPROVED":
if current_user.id == initiated_user.id:
raise serializers.ValidationError(
"Same user cannot approve the association request"
Expand All @@ -120,3 +119,9 @@ def update(self, instance, validated_data):
instance.priority = validated_data["priority"]
instance.save()
return instance


class BenchmarkListofModelsSerializer(serializers.ModelSerializer):
class Meta:
model = BenchmarkModel
fields = ["model_mlcube", "approval_status", "created_at"]
17 changes: 17 additions & 0 deletions server/mlcube/migrations/0002_alter_mlcube_unique_together.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 3.2.20 on 2023-11-16 23:12

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('mlcube', '0001_initial'),
]

operations = [
migrations.AlterUniqueTogether(
name='mlcube',
unique_together={('image_tarball_hash', 'image_hash', 'additional_files_tarball_hash', 'mlcube_hash', 'parameters_hash')},
),
]
4 changes: 0 additions & 4 deletions server/mlcube/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,10 @@ def __str__(self):
class Meta:
unique_together = (
(
"image_tarball_url",
"image_tarball_hash",
"image_hash",
"additional_files_tarball_url",
"additional_files_tarball_hash",
"git_mlcube_url",
"mlcube_hash",
"git_parameters_url",
"parameters_hash",
),
)
Expand Down
102 changes: 64 additions & 38 deletions server/mlcube/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,62 @@
from .models import MlCube


def validate_optional_mlcube_components(data):
git_parameters_url = data["git_parameters_url"]
parameters_hash = data["parameters_hash"]

additional_files_tarball_url = data["additional_files_tarball_url"]
additional_files_tarball_hash = data["additional_files_tarball_hash"]

image_hash = data["image_hash"]

image_tarball_url = data["image_tarball_url"]
image_tarball_hash = data["image_tarball_hash"]

# validate nonblank parameters file hash
if git_parameters_url and not parameters_hash:
raise serializers.ValidationError("Parameters require file hash")

if not git_parameters_url and parameters_hash:
raise serializers.ValidationError("Paramters hash was provided without URL")

# validate nonblank additional files hash
if additional_files_tarball_url and not additional_files_tarball_hash:
raise serializers.ValidationError("Additional files require file hash")

if not additional_files_tarball_url and additional_files_tarball_hash:
raise serializers.ValidationError(
"Additional files hash was provided without URL"
)

# validate images attributes.
if not image_hash and not image_tarball_hash:
raise serializers.ValidationError(
"Image hash or Image tarball hash must be provided"
)
if image_hash and image_tarball_hash:
raise serializers.ValidationError(
"Image hash and Image tarball hash can't be provided at the same time"
)
if image_tarball_url and not image_tarball_hash:
raise serializers.ValidationError(
"Providing Image tarball requires providing image tarball hash"
)

if not image_tarball_url and image_tarball_hash:
raise serializers.ValidationError(
"image tarball hash should not be provided if no image tarball url is provided"
)


class MlCubeSerializer(serializers.ModelSerializer):
class Meta:
model = MlCube
fields = "__all__"
read_only_fields = ["owner"]

def validate(self, data):
git_parameters_url = data["git_parameters_url"]
parameters_hash = data["parameters_hash"]

additional_files_tarball_url = data["additional_files_tarball_url"]
additional_files_tarball_hash = data["additional_files_tarball_hash"]

image_hash = data["image_hash"]

image_tarball_url = data["image_tarball_url"]
image_tarball_hash = data["image_tarball_hash"]

# validate nonblank parameters file hash
if git_parameters_url and not parameters_hash:
raise serializers.ValidationError("Parameters require file hash")

# validate nonblank additional files hash
if additional_files_tarball_url and not additional_files_tarball_hash:
raise serializers.ValidationError("Additional files require file hash")

# validate images attributes.
if not image_hash and not image_tarball_hash:
raise serializers.ValidationError(
"Image hash or Image tarball hash must be provided"
)
if image_hash and image_tarball_hash:
raise serializers.ValidationError(
"Image hash and Image tarball hash can't be provided at the same time"
)
if image_tarball_url and not image_tarball_hash:
raise serializers.ValidationError(
"Providing Image tarball requires providing image tarball hash"
)

if not image_tarball_url and image_tarball_hash:
raise serializers.ValidationError(
"image tarball hash should not be provided if no image tarball url is provided"
)

validate_optional_mlcube_components(data)
return data


Expand All @@ -72,4 +83,19 @@ def validate(self, data):
raise serializers.ValidationError(
"User cannot update non editable fields in Operation mode"
)

updated_dict = {}
for key in [
"git_parameters_url",
"parameters_hash",
"additional_files_tarball_url",
"additional_files_tarball_hash",
"image_hash",
"image_tarball_url",
"image_tarball_hash",
]:
updated_dict[key] = data.get(key, getattr(self.instance, key))

validate_optional_mlcube_components(updated_dict)

return data
16 changes: 16 additions & 0 deletions server/result/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,19 @@ def validate(self, data):
"Dataset-Benchmark association must be approved"
)
return data


# TODO: define what should be editable, and WHO can do that
class ModelResultDetailSerializer(serializers.ModelSerializer):
class Meta:
model = ModelResult
fields = "__all__"
read_only_fields = [
"owner",
"approved_at",
"approval_status",
"benchmark",
"model",
"dataset",
"results",
]
9 changes: 5 additions & 4 deletions server/result/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from drf_spectacular.utils import extend_schema

from .models import ModelResult
from .serializers import ModelResultSerializer
from .serializers import ModelResultSerializer, ModelResultDetailSerializer
from .permissions import IsAdmin, IsBenchmarkOwner, IsDatasetOwner, IsResultOwner


Expand Down Expand Up @@ -42,9 +42,10 @@ def post(self, request, format=None):


class ModelResultDetail(GenericAPIView):
serializer_class = ModelResultSerializer
serializer_class = ModelResultDetailSerializer
queryset = ""

# TODO: fix delete permissions?
def get_permissions(self):
if self.request.method == "PUT" or self.request.method == "DELETE":
self.permission_classes = [IsAdmin | IsResultOwner]
Expand All @@ -63,15 +64,15 @@ def get(self, request, pk, format=None):
Retrieve a result instance.
"""
modelresult = self.get_object(pk)
serializer = ModelResultSerializer(modelresult)
serializer = ModelResultDetailSerializer(modelresult)
return Response(serializer.data)

def put(self, request, pk, format=None):
"""
Update a result instance.
"""
modelresult = self.get_object(pk)
serializer = ModelResultSerializer(modelresult, data=request.data)
serializer = ModelResultDetailSerializer(modelresult, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
Expand Down
1 change: 1 addition & 0 deletions server/user/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
User = get_user_model()


# TODO: define what can be edited, and how
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
Expand Down
Loading

0 comments on commit 26507cc

Please sign in to comment.