This is an automated email from the ASF dual-hosted git repository.

yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


The following commit(s) were added to refs/heads/main by this push:
     new 7bacd99d6 refactor(portal): make data-product serializers proto-native 
(Track D) (#203)
7bacd99d6 is described below

commit 7bacd99d69a15b89a1dc2398b55c863eb1cadfe6
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 04:48:46 2026 -0400

    refactor(portal): make data-product serializers proto-native (Track D) 
(#203)
    
    Rewrite DataProductSerializer + DataReplicaLocationSerializer to read the 
gRPC
    DataProductModel / DataReplicaLocationModel protobuf directly, emitting the 
same
    Thrift-named JSON keys.
    
    - dataProductType / replicaLocationCategory / replicaPersistentType render 
as
      Thrift ints via the prefix-aligned enum field helpers; the serializer's 
extra
      modifiedTime maps to the proto last_modified_time alongside 
lastModifiedTime;
      optional proto strings -> null; the method fields (downloadURL / 
isInputFileUpload
      / filesize / userHasWriteAccess) read proto fields.
    - Repoint every data-product consumer to pass protobuf through directly:
      DataProductView (get/put), the download/delete endpoints, 
FullExperimentViewSet's
      input/output data products, output_views._generate_data, 
workspace.create_experiment,
      and view_utils.DataProductSharedDirPermission.
    - Move the data_product_file_path helper to view_utils (proto-reading); 
remove the
      grpc_adapters data_product / _data_replica_location / 
data_product_file_path
      funcs + the now-unused Thrift DataProductModel/DataReplicaLocationModel 
and
      replica-enum imports.
    
    Validated byte-for-byte (full data product w/ replica, empty) vs the old
    adapter+serializer path. manage.py check green; api test failures unchanged.
---
 .../django_airavata/apps/api/grpc_adapters.py      |  70 -------------
 .../django_airavata/apps/api/output_views.py       |   7 +-
 .../django_airavata/apps/api/serializers.py        | 116 ++++++++++++++++-----
 .../django_airavata/apps/api/view_utils.py         |  27 ++++-
 .../django_airavata/apps/api/views.py              |  41 ++++----
 .../django_airavata/apps/workspace/views.py        |   6 +-
 6 files changed, 136 insertions(+), 131 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py 
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index a59bb8b62..829a76c98 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -16,11 +16,6 @@ from types import SimpleNamespace
 from airavata.model.appcatalog.computeresource.ttypes import (
     JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
 )
-from airavata.model.data.replica.ttypes import (
-    DataProductType as _ThriftDataProductType,
-    ReplicaLocationCategory as _ThriftReplicaLocationCategory,
-    ReplicaPersistentType as _ThriftReplicaPersistentType,
-)
 from airavata.model.data.movement.ttypes import (
     DataMovementProtocol as _ThriftDataMovementProtocol,
 )
@@ -105,71 +100,6 @@ def group(pb):
     )
 
 
-def _data_replica_location(pb):
-    """gRPC ``DataReplicaLocationModel`` -> serializer shape."""
-    return SimpleNamespace(
-        replicaId=pb.replica_id or None,
-        productUri=pb.product_uri or None,
-        replicaName=pb.replica_name or None,
-        replicaDescription=pb.replica_description or None,
-        creationTime=pb.creation_time or None,
-        lastModifiedTime=pb.last_modified_time or None,
-        validUntilTime=pb.valid_until_time or None,
-        replicaLocationCategory=_thrift_enum_prefixed(
-            pb, 'replica_location_category', _ThriftReplicaLocationCategory,
-            'REPLICA_LOCATION_CATEGORY_'),
-        replicaPersistentType=_thrift_enum_prefixed(
-            pb, 'replica_persistent_type', _ThriftReplicaPersistentType,
-            'REPLICA_PERSISTENT_TYPE_'),
-        storageResourceId=pb.storage_resource_id or None,
-        filePath=pb.file_path or None,
-        replicaMetadata=dict(pb.replica_metadata),
-    )
-
-
-def data_product_file_path(data_product):
-    """First replica's ``filePath`` from an adapted data product, or None.
-
-    The gRPC ``storage`` facade expects the FULL FILE PATH, absolute or
-    ``~/``-prefixed (a bare relative path NPEs server-side, as ``resolvePath``
-    expands ``~/`` to the storage root). Replica file paths are typically
-    absolute (e.g. ``/storage/tmp/<file>``); a relative one is ``~/``-prefixed.
-    Pass an adapted ``DataProductModel`` (``grpc_adapters.data_product``).
-    """
-    replicas = getattr(data_product, 'replicaLocations', None) or []
-    if not replicas:
-        return None
-    file_path = getattr(replicas[0], 'filePath', None)
-    if not file_path:
-        return None
-    if not (file_path.startswith('/') or file_path.startswith('~/')):
-        file_path = '~/' + file_path
-    return file_path
-
-
-def data_product(pb):
-    """gRPC ``DataProductModel`` -> ``DataProductSerializer`` shape."""
-    return SimpleNamespace(
-        productUri=pb.product_uri,
-        gatewayId=pb.gateway_id,
-        parentProductUri=pb.parent_product_uri or None,
-        productName=pb.product_name or None,
-        productDescription=pb.product_description or None,
-        ownerName=pb.owner_name or None,
-        dataProductType=_thrift_enum_prefixed(
-            pb, 'data_product_type', _ThriftDataProductType,
-            'DATA_PRODUCT_TYPE_'),
-        productSize=pb.product_size or None,
-        creationTime=pb.creation_time or None,
-        # the serializer declares both modifiedTime and lastModifiedTime
-        modifiedTime=pb.last_modified_time or None,
-        lastModifiedTime=pb.last_modified_time or None,
-        productMetadata=dict(pb.product_metadata),
-        replicaLocations=[
-            _data_replica_location(r) for r in pb.replica_locations],
-    )
-
-
 def user_profile(pb):
     """gRPC ``UserProfile`` -> ``UserProfileSerializer`` shape.
 
diff --git a/airavata-django-portal/django_airavata/apps/api/output_views.py 
b/airavata-django-portal/django_airavata/apps/api/output_views.py
index 2820d34f8..0df73208f 100644
--- a/airavata-django-portal/django_airavata/apps/api/output_views.py
+++ b/airavata-django-portal/django_airavata/apps/api/output_views.py
@@ -14,7 +14,7 @@ from 
airavata_sdk.generated.org.apache.airavata.model.application.io.application
 from django.conf import settings
 from nbconvert import HTMLExporter
 
-from . import grpc_adapters
+from . import view_utils
 
 logger = logging.getLogger(__name__)
 
@@ -220,11 +220,10 @@ def _generate_data(request,
           experiment_output.value.startswith("airavata-dp")):
         data_product_uris = experiment_output.value.split(",")
         data_products = map(
-            lambda dpid: grpc_adapters.data_product(
-                request.airavata.research.get_data_product(dpid)),
+            lambda dpid: request.airavata.research.get_data_product(dpid),
             data_product_uris)
         for data_product in data_products:
-            file_path = grpc_adapters.data_product_file_path(data_product)
+            file_path = view_utils.data_product_file_path(data_product)
             if file_path and request.airavata.storage.file_exists(file_path):
                 resp = request.airavata.storage.download_file(file_path)
                 output_file = io.BytesIO(resp.content)
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 35476867c..1ad7103f5 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -15,10 +15,6 @@ from airavata.model.appcatalog.groupresourceprofile.ttypes 
import (
     AwsComputeResourcePreference
 )
 from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
-from airavata.model.data.replica.ttypes import (
-    DataProductModel,
-    DataReplicaLocationModel
-)
 from airavata.model.group.ttypes import GroupModel, ResourcePermissionType
 from airavata.model.status.ttypes import (
     ExperimentState
@@ -1699,23 +1695,91 @@ def _user_configuration_request(d):
     return e.UserConfigurationDataModel(**kwargs)
 
 
-class DataReplicaLocationSerializer(
-        thrift_utils.create_serializer_class(DataReplicaLocationModel)):
-    creationTime = UTCPosixTimestampDateTimeField()
-    lastModifiedTime = UTCPosixTimestampDateTimeField()
+def _replica_catalog_pb2():
+    from airavata_sdk.generated.org.apache.airavata.model.data.replica import (
+        replica_catalog_pb2,
+    )
+    return replica_catalog_pb2
 
 
-class DataProductSerializer(
-        thrift_utils.create_serializer_class(DataProductModel)):
-    creationTime = UTCPosixTimestampDateTimeField()
-    modifiedTime = UTCPosixTimestampDateTimeField()
-    lastModifiedTime = UTCPosixTimestampDateTimeField()
-    replicaLocations = DataReplicaLocationSerializer(many=True)
+def _data_product_type_field(**kwargs):
+    from airavata.model.data.replica.ttypes import DataProductType as _T
+    return proto_enum_int_field(
+        _replica_catalog_pb2().DataProductType.DESCRIPTOR, _T,
+        proto_prefix='DATA_PRODUCT_TYPE_', **kwargs)
+
+
+def _replica_location_category_field(**kwargs):
+    from airavata.model.data.replica.ttypes import ReplicaLocationCategory as 
_T
+    return proto_enum_int_field(
+        _replica_catalog_pb2().ReplicaLocationCategory.DESCRIPTOR, _T,
+        proto_prefix='REPLICA_LOCATION_CATEGORY_', **kwargs)
+
+
+def _replica_persistent_type_field(**kwargs):
+    from airavata.model.data.replica.ttypes import ReplicaPersistentType as _T
+    return proto_enum_int_field(
+        _replica_catalog_pb2().ReplicaPersistentType.DESCRIPTOR, _T,
+        proto_prefix='REPLICA_PERSISTENT_TYPE_', **kwargs)
+
+
+class DataReplicaLocationSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``DataReplicaLocationModel``."""
+
+    replicaId = serializers.CharField(source='replica_id', allow_blank=True, 
allow_null=True, required=False)
+    productUri = serializers.CharField(source='product_uri', allow_blank=True, 
allow_null=True, required=False)
+    replicaName = serializers.CharField(source='replica_name', 
allow_blank=True, allow_null=True, required=False)
+    replicaDescription = serializers.CharField(source='replica_description', 
allow_blank=True, allow_null=True, required=False)
+    creationTime = ProtoTimestampField(source='creation_time', 
null_if_zero=True)
+    lastModifiedTime = ProtoTimestampField(source='last_modified_time', 
null_if_zero=True)
+    validUntilTime = ProtoIntOrNoneField(source='valid_until_time')
+    replicaLocationCategory = 
_replica_location_category_field(source='replica_location_category', 
required=False, allow_null=True)
+    replicaPersistentType = 
_replica_persistent_type_field(source='replica_persistent_type', 
required=False, allow_null=True)
+    storageResourceId = serializers.CharField(source='storage_resource_id', 
allow_blank=True, allow_null=True, required=False)
+    filePath = serializers.CharField(source='file_path', allow_blank=True, 
allow_null=True, required=False)
+    replicaMetadata = serializers.DictField(source='replica_metadata', 
required=False)
+
+    def to_representation(self, instance):
+        ret = super().to_representation(instance)
+        # The old adapter mapped these optional proto strings to None when 
empty.
+        for f in ('replicaId', 'productUri', 'replicaName', 
'replicaDescription',
+                  'storageResourceId', 'filePath'):
+            if ret.get(f) == '':
+                ret[f] = None
+        return ret
+
+
+class DataProductSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``DataProductModel``."""
+
+    productUri = serializers.CharField(source='product_uri', allow_blank=True, 
allow_null=True, required=False)
+    gatewayId = serializers.CharField(source='gateway_id', allow_blank=True, 
allow_null=True, required=False)
+    parentProductUri = serializers.CharField(source='parent_product_uri', 
allow_blank=True, allow_null=True, required=False)
+    productName = serializers.CharField(source='product_name', 
allow_blank=True, allow_null=True, required=False)
+    productDescription = serializers.CharField(source='product_description', 
allow_blank=True, allow_null=True, required=False)
+    ownerName = serializers.CharField(source='owner_name', allow_blank=True, 
allow_null=True, required=False)
+    dataProductType = _data_product_type_field(source='data_product_type', 
required=False, allow_null=True)
+    productSize = ProtoIntOrNoneField(source='product_size')
+    creationTime = ProtoTimestampField(source='creation_time', 
null_if_zero=True)
+    # The serializer declares both modifiedTime and lastModifiedTime; both map 
to
+    # the proto last_modified_time.
+    modifiedTime = ProtoTimestampField(source='last_modified_time', 
null_if_zero=True)
+    lastModifiedTime = ProtoTimestampField(source='last_modified_time', 
null_if_zero=True)
+    productMetadata = serializers.DictField(source='product_metadata', 
required=False)
+    replicaLocations = 
DataReplicaLocationSerializer(source='replica_locations', many=True, 
required=False)
     downloadURL = serializers.SerializerMethodField()
     isInputFileUpload = serializers.SerializerMethodField()
     filesize = serializers.SerializerMethodField()
     userHasWriteAccess = serializers.SerializerMethodField()
 
+    def to_representation(self, instance):
+        ret = super().to_representation(instance)
+        for f in ('parentProductUri', 'productName', 'productDescription',
+                  'ownerName'):
+            if ret.get(f) == '':
+                ret[f] = None
+        return ret
+
     def get_downloadURL(self, data_product):
         """Lazy portal URL to the byte-streaming download endpoint.
 
@@ -1723,11 +1787,11 @@ class DataProductSerializer(
         is deferred to the endpoint, so this getter makes no backend call.
         """
         request = self.context['request']
-        if not getattr(data_product, 'replicaLocations', None):
+        if not data_product.replica_locations:
             return None
         base = request.build_absolute_uri(
             reverse('django_airavata_api:download-file'))
-        return base + '?data-product-uri=' + quote(data_product.productUri)
+        return base + '?data-product-uri=' + quote(data_product.product_uri)
 
     def get_isInputFileUpload(self, data_product):
         """Return True if this is an uploaded input file.
@@ -1737,17 +1801,17 @@ class DataProductSerializer(
         (TMP_INPUT_FILE_UPLOAD_DIR == "tmp"), so the first replica's file path
         has that directory as its immediate parent.
         """
-        replicas = getattr(data_product, 'replicaLocations', None) or []
-        if not replicas or not replicas[0].filePath:
+        replicas = data_product.replica_locations
+        if not replicas or not replicas[0].file_path:
             return False
-        parent = os.path.dirname(replicas[0].filePath)
+        parent = os.path.dirname(replicas[0].file_path)
         return os.path.basename(parent) == TMP_INPUT_FILE_UPLOAD_DIR
 
     def get_filesize(self, data_product):
-        # productSize comes from the data product registry; no backend call.
-        return getattr(data_product, 'productSize', None) or 0
+        # product_size comes from the data product registry; no backend call.
+        return data_product.product_size or 0
 
-    def get_userHasWriteAccess(self, data_product: DataProductModel):
+    def get_userHasWriteAccess(self, data_product):
         """Whether the requesting user may write this data product.
 
         Derived without a backend file-metadata call: the owner always has
@@ -1755,12 +1819,12 @@ class DataProductSerializer(
         (a user's own private storage) write is allowed.
         """
         request = self.context['request']
-        owner = getattr(data_product, 'ownerName', None)
+        owner = data_product.owner_name
         if owner and owner == request.user.username:
             return True
-        replicas = getattr(data_product, 'replicaLocations', None) or []
-        if replicas and replicas[0].filePath:
-            if view_utils.is_shared_path(replicas[0].filePath):
+        replicas = data_product.replica_locations
+        if replicas and replicas[0].file_path:
+            if view_utils.is_shared_path(replicas[0].file_path):
                 # Only admins can edit files in a shared directory.
                 return request.is_gateway_admin
         return True
diff --git a/airavata-django-portal/django_airavata/apps/api/view_utils.py 
b/airavata-django-portal/django_airavata/apps/api/view_utils.py
index e77c5db75..57003f837 100644
--- a/airavata-django-portal/django_airavata/apps/api/view_utils.py
+++ b/airavata-django-portal/django_airavata/apps/api/view_utils.py
@@ -14,8 +14,6 @@ from rest_framework.reverse import reverse
 from rest_framework.utils.urls import remove_query_param, replace_query_param
 from rest_framework.viewsets import GenericViewSet
 
-from . import grpc_adapters
-
 logger = logging.getLogger(__name__)
 
 
@@ -271,13 +269,32 @@ class BaseSharedDirPermission(permissions.BasePermission):
         return True
 
 
+def data_product_file_path(data_product):
+    """First replica's ``file_path`` from a proto ``DataProductModel``, or 
None.
+
+    The gRPC ``storage`` facade expects the FULL FILE PATH, absolute or
+    ``~/``-prefixed (a bare relative path NPEs server-side, as ``resolvePath``
+    expands ``~/`` to the storage root). Replica file paths are typically
+    absolute (e.g. ``/storage/tmp/<file>``); a relative one is ``~/``-prefixed.
+    """
+    replicas = data_product.replica_locations
+    if not replicas:
+        return None
+    file_path = replicas[0].file_path
+    if not file_path:
+        return None
+    if not (file_path.startswith('/') or file_path.startswith('~/')):
+        file_path = '~/' + file_path
+    return file_path
+
+
 class DataProductSharedDirPermission(BaseSharedDirPermission):
     def get_path(self, request, view) -> str:
         data_product_uri = request.query_params.get(
             'data-product-uri', request.query_params.get('product-uri', ''))
-        data_product = grpc_adapters.data_product(
-            request.airavata.research.get_data_product(data_product_uri))
-        file_path = grpc_adapters.data_product_file_path(data_product)
+        data_product = request.airavata.research.get_data_product(
+            data_product_uri)
+        file_path = data_product_file_path(data_product)
         return file_path or ""
 
 
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 509a0617e..682882a85 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -77,6 +77,10 @@ def _data_type_pb2():
     return application_io_pb2
 
 
+# First replica's ~/-prefixed file path from a proto DataProductModel.
+_data_product_file_path = view_utils.data_product_file_path
+
+
 def _storage_upload_and_register(request, dir_path, uploaded_file, name=None,
                                  content_type=None, experiment_id=None):
     """Upload a file to user storage and register a data product for it (gRPC).
@@ -111,8 +115,7 @@ def _storage_upload_and_register(request, dir_path, 
uploaded_file, name=None,
             storage_resource_id=storage.get_default_storage_resource_id(),
             content_type=content_type,
             product_size=metadata.size))
-    return grpc_adapters.data_product(
-        request.airavata.research.get_data_product(product_uri))
+    return request.airavata.research.get_data_product(product_uri)
 
 
 class GroupViewSet(APIBackedViewSet):
@@ -372,15 +375,13 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
             lookup_value)
         DT = _data_type_pb2().DataType
         outputDataProducts = [
-            grpc_adapters.data_product(
-                self.request.airavata.research.get_data_product(output.value))
+            self.request.airavata.research.get_data_product(output.value)
             for output in experimentModel.experiment_outputs
             if (output.value and
                 output.value.startswith('airavata-dp') and
                 output.type in (DT.URI, DT.STDOUT, DT.STDERR))]
         outputDataProducts += [
-            grpc_adapters.data_product(
-                self.request.airavata.research.get_data_product(dp))
+            self.request.airavata.research.get_data_product(dp)
             for output in experimentModel.experiment_outputs
             if (output.value and
                 output.type == DT.URI_COLLECTION)
@@ -397,15 +398,13 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
         exp_output_views = output_views.get_output_views(
             self.request, experimentModel, applicationInterface)
         inputDataProducts = [
-            grpc_adapters.data_product(
-                self.request.airavata.research.get_data_product(inp.value))
+            self.request.airavata.research.get_data_product(inp.value)
             for inp in experimentModel.experiment_inputs
             if (inp.value and
                 inp.value.startswith('airavata-dp') and
                 inp.type in (DT.URI, DT.STDOUT, DT.STDERR))]
         inputDataProducts += [
-            grpc_adapters.data_product(
-                self.request.airavata.research.get_data_product(dp))
+            self.request.airavata.research.get_data_product(dp)
             for inp in experimentModel.experiment_inputs
             if (inp.value and
                 inp.type == DT.URI_COLLECTION)
@@ -825,25 +824,23 @@ class DataProductView(APIView):
 
     def get(self, request, format=None):
         data_product_uri = request.query_params['product-uri']
-        data_product = grpc_adapters.data_product(
-            request.airavata.research.get_data_product(data_product_uri))
+        data_product = 
request.airavata.research.get_data_product(data_product_uri)
         serializer = self.serializer_class(
             data_product, context={'request': request})
         return Response(serializer.data)
 
     def put(self, request, format=None):
         data_product_uri = request.query_params['product-uri']
-        data_product = grpc_adapters.data_product(
-            request.airavata.research.get_data_product(data_product_uri))
+        data_product = 
request.airavata.research.get_data_product(data_product_uri)
         if request.data and "fileContentText" in request.data:
-            file_path = grpc_adapters.data_product_file_path(data_product)
+            file_path = _data_product_file_path(data_product)
             if file_path is None:
                 return Response(status=status.HTTP_400_BAD_REQUEST)
             # Overwrite the file content in place at the replica's path.
             request.airavata.storage.upload_file(
                 path=file_path,
                 content=request.data["fileContentText"].encode("utf-8"),
-                name=data_product.productName or os.path.basename(file_path))
+                name=data_product.product_name or os.path.basename(file_path))
             return self.get(request=request, format=format)
         else:
             return Response(status=status.HTTP_400_BAD_REQUEST)
@@ -909,17 +906,16 @@ def download(request):
     """
     data_product_uri = request.GET.get('data-product-uri', '')
     try:
-        data_product = grpc_adapters.data_product(
-            request.airavata.research.get_data_product(data_product_uri))
+        data_product = 
request.airavata.research.get_data_product(data_product_uri)
     except Exception as e:
         log.warning("Failed to load DataProduct for {}".format(
             data_product_uri), exc_info=True, extra={'request': request})
         raise Http404("data product does not exist") from e
-    file_path = grpc_adapters.data_product_file_path(data_product)
+    file_path = _data_product_file_path(data_product)
     if file_path is None:
         raise Http404("data product has no replica to download")
     resp = request.airavata.storage.download_file(file_path)
-    file_name = resp.name or data_product.productName or 
os.path.basename(file_path)
+    file_name = resp.name or data_product.product_name or 
os.path.basename(file_path)
     response = FileResponse(
         io.BytesIO(resp.content),
         as_attachment=False,
@@ -935,8 +931,7 @@ def delete_file(request):
     data_product_uri = request.GET.get('data-product-uri', '')
     data_product = None
     try:
-        data_product = grpc_adapters.data_product(
-            request.airavata.research.get_data_product(data_product_uri))
+        data_product = 
request.airavata.research.get_data_product(data_product_uri)
     except Exception as e:
         log.warning("Failed to load DataProduct for {}"
                     .format(data_product_uri), exc_info=True)
@@ -945,7 +940,7 @@ def delete_file(request):
         if (data_product.gatewayId != settings.GATEWAY_ID or
                 data_product.ownerName != request.user.username):
             raise PermissionDenied()
-        file_path = grpc_adapters.data_product_file_path(data_product)
+        file_path = _data_product_file_path(data_product)
         if file_path is None:
             raise Http404("data product has no replica to delete")
         request.airavata.storage.delete_file(file_path)
diff --git a/airavata-django-portal/django_airavata/apps/workspace/views.py 
b/airavata-django-portal/django_airavata/apps/workspace/views.py
index 64120977d..54fbe55cd 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/views.py
+++ b/airavata-django-portal/django_airavata/apps/workspace/views.py
@@ -9,7 +9,7 @@ from django.shortcuts import render
 from django.utils.module_loading import import_string
 from rest_framework.renderers import JSONRenderer
 
-from django_airavata.apps.api import grpc_adapters, models
+from django_airavata.apps.api import models, view_utils
 from django_airavata.apps.api.views import (
     ApplicationModuleViewSet,
     ExperimentSearchViewSet,
@@ -93,9 +93,9 @@ def create_experiment(request, app_module_id):
                 if user_file_url.scheme == 'airavata-dp':
                     dp_uri = user_file_value
                     try:
-                        data_product = grpc_adapters.data_product(
+                        data_product = (
                             request.airavata.research.get_data_product(dp_uri))
-                        file_path = grpc_adapters.data_product_file_path(
+                        file_path = view_utils.data_product_file_path(
                             data_product)
                         if file_path and request.airavata.storage.file_exists(
                                 file_path):

Reply via email to