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 858bef445 refactor(portal): make notification + parser serializers 
proto-native (Track D) (#197)
858bef445 is described below

commit 858bef445e9b709bf6ccbb51ff6443d9d5799d8f
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 03:51:27 2026 -0400

    refactor(portal): make notification + parser serializers proto-native 
(Track D) (#197)
    
    Rewrite NotificationSerializer and ParserSerializer (+ nested 
ParserInput/Output)
    to read the gRPC Notification / Parser protobuf directly, emitting the same
    Thrift-named JSON keys.
    
    - Notification: priority renders as the member name; showInDashboard moves 
from a
      proto setattr (impossible on a protobuf message) to a 
SerializerMethodField that
      reads the NotificationExtension table; create/update build the proto 
directly.
      Also rewrite context_processors.get_notifications to read proto fields 
directly
      (it consumed the adapter output, not the serializer) — priority bridged 
to the
      Thrift int the dashboard expects, byte-for-byte.
    - Parser: type (IOType) renders as the Thrift int via the new 
ProtoEnumIntField
      (+ proto_enum_int_field factory: proto enum -> Thrift int by name); nested
      inputs/outputs round-trip on read and write.
    - Repoint ManageNotificationViewSet + ParserViewSet to pass protobuf 
through,
      dropping grpc_adapters.notification/parser + 
grpc_requests.notification/parser
      and the now-unused Thrift Notification/NotificationPriority/IOType 
imports.
    
    Validated byte-for-byte (notification full/low, parser w/ nested I/O, the
    notifications context processor) vs the old adapter+serializer path; write 
paths
    (create/update incl. nested + enum bridges) produce equivalent protos.
    manage.py check green; api test failures unchanged vs origin/main.
---
 .../django_airavata/apps/api/grpc_adapters.py      |  60 ------
 .../django_airavata/apps/api/grpc_requests.py      |  62 ------
 .../django_airavata/apps/api/serializers.py        | 222 ++++++++++++++++++---
 .../django_airavata/apps/api/views.py              |  40 ++--
 .../django_airavata/context_processors.py          |  51 +++--
 5 files changed, 252 insertions(+), 183 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 47c5774ac..2edda54b4 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -26,7 +26,6 @@ from airavata.model.appcatalog.groupresourceprofile.ttypes 
import (
 from airavata.model.appcatalog.parallelism.ttypes import (
     ApplicationParallelismType as _ThriftParallelismType,
 )
-from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
 from airavata.model.application.io.ttypes import DataType as _ThriftDataType
 from airavata.model.data.replica.ttypes import (
     DataProductType as _ThriftDataProductType,
@@ -48,9 +47,6 @@ from airavata.model.status.ttypes import (
 )
 from airavata.model.task.ttypes import TaskTypes as _ThriftTaskTypes
 from airavata.model.user.ttypes import Status as _ThriftStatus
-from airavata.model.workspace.ttypes import (
-    NotificationPriority as _ThriftNotificationPriority,
-)
 
 
 def _thrift_enum(pb, field, thrift_enum):
@@ -858,62 +854,6 @@ def user_profile(pb):
     )
 
 
-def notification(pb):
-    """gRPC ``Notification`` -> ``NotificationSerializer`` shape."""
-    return SimpleNamespace(
-        notificationId=pb.notification_id,
-        gatewayId=pb.gateway_id,
-        title=pb.title,
-        notificationMessage=pb.notification_message,
-        creationTime=pb.creation_time or None,
-        # publishedTime/expirationTime use non-nullable UTC fields -> keep int.
-        publishedTime=pb.published_time,
-        expirationTime=pb.expiration_time,
-        # priority renders via ThriftEnumField (the NAME), so produce the 
Thrift
-        # member; proto prefixes only the zero UNKNOWN sentinel.
-        priority=_thrift_enum_prefixed(
-            pb, 'priority', _ThriftNotificationPriority, 
'NOTIFICATION_PRIORITY_'),
-    )
-
-
-def _parser_input(pb):
-    """gRPC ``ParserInput`` -> auto-generated serializer shape."""
-    return SimpleNamespace(
-        id=pb.id,
-        name=pb.name,
-        requiredInput=pb.required_input,
-        parserId=pb.parser_id,
-        # type (IOType) renders as a raw int; bridge by name (proto 
FILE/PROPERTY
-        # align, only IO_TYPE_UNKNOWN is prefixed).
-        type=_thrift_enum_prefixed(pb, 'type', _ThriftIOType, 'IO_TYPE_'),
-    )
-
-
-def _parser_output(pb):
-    """gRPC ``ParserOutput`` -> auto-generated serializer shape."""
-    return SimpleNamespace(
-        id=pb.id,
-        name=pb.name,
-        requiredOutput=pb.required_output,
-        parserId=pb.parser_id,
-        type=_thrift_enum_prefixed(pb, 'type', _ThriftIOType, 'IO_TYPE_'),
-    )
-
-
-def parser(pb):
-    """gRPC ``Parser`` -> ``ParserSerializer`` shape."""
-    return SimpleNamespace(
-        id=pb.id,
-        imageName=pb.image_name,
-        outputDirPath=pb.output_dir_path,
-        inputDirPath=pb.input_dir_path,
-        executionCommand=pb.execution_command,
-        inputFiles=[_parser_input(i) for i in pb.input_files],
-        outputFiles=[_parser_output(o) for o in pb.output_files],
-        gatewayId=pb.gateway_id,
-    )
-
-
 # --- Per-protocol job-submission / data-movement interface details ----------
 # These admin-only detail views render a single protocol's submission/movement
 # model via the auto-generated serializer, so the adapter exposes Thrift 
attribute
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py 
b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
index f596dd65e..872e18a70 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
@@ -23,7 +23,6 @@ from airavata.model.appcatalog.parallelism.ttypes import (
 from airavata.model.appcatalog.groupresourceprofile.ttypes import (
     ResourceType as _ThriftResourceType,
 )
-from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
 from airavata.model.application.io.ttypes import DataType as _ThriftDataType
 from airavata.model.data.movement.ttypes import (
     DataMovementProtocol as _ThriftDataMovementProtocol,
@@ -31,9 +30,6 @@ from airavata.model.data.movement.ttypes import (
 from airavata.model.experiment.ttypes import (
     ExperimentType as _ThriftExperimentType,
 )
-from airavata.model.workspace.ttypes import (
-    NotificationPriority as _ThriftNotificationPriority,
-)
 
 _GEN = "airavata_sdk.generated.org.apache.airavata.model"
 
@@ -42,10 +38,6 @@ def _pb2(path):
     return importlib.import_module(f"{_GEN}.{path}")
 
 
-def _workspace_pb2():
-    return _pb2("workspace.workspace_pb2")
-
-
 def _proto_enum(proto_enum, thrift_enum, value, prefix=''):
     """Thrift enum value -> proto enum value, by name (mirror of the read-side
     ``grpc_adapters`` enum bridges).
@@ -174,60 +166,6 @@ def application_deployment(t):
     )
 
 
-def notification(t):
-    """Thrift ``Notification`` -> proto ``Notification`` request message."""
-    return _workspace_pb2().Notification(
-        notification_id=t.notificationId or '',
-        gateway_id=t.gatewayId or '',
-        title=t.title or '',
-        notification_message=t.notificationMessage or '',
-        creation_time=t.creationTime or 0,
-        published_time=t.publishedTime or 0,
-        expiration_time=t.expirationTime or 0,
-        priority=_proto_enum(
-            _workspace_pb2().NotificationPriority, _ThriftNotificationPriority,
-            t.priority, 'NOTIFICATION_PRIORITY_'),
-    )
-
-
-def _parser_input(t):
-    """Thrift ``ParserInput`` -> proto ``ParserInput``."""
-    pp = _pb2("appcatalog.parser.parser_pb2")
-    return pp.ParserInput(
-        id=t.id or '',
-        name=t.name or '',
-        required_input=bool(t.requiredInput),
-        parser_id=t.parserId or '',
-        type=_proto_enum(pp.IOType, _ThriftIOType, t.type, 'IO_TYPE_'),
-    )
-
-
-def _parser_output(t):
-    """Thrift ``ParserOutput`` -> proto ``ParserOutput``."""
-    pp = _pb2("appcatalog.parser.parser_pb2")
-    return pp.ParserOutput(
-        id=t.id or '',
-        name=t.name or '',
-        required_output=bool(t.requiredOutput),
-        parser_id=t.parserId or '',
-        type=_proto_enum(pp.IOType, _ThriftIOType, t.type, 'IO_TYPE_'),
-    )
-
-
-def parser(t):
-    """Thrift ``Parser`` -> proto ``Parser`` request message."""
-    return _pb2("appcatalog.parser.parser_pb2").Parser(
-        id=t.id or '',
-        image_name=t.imageName or '',
-        output_dir_path=t.outputDirPath or '',
-        input_dir_path=t.inputDirPath or '',
-        execution_command=t.executionCommand or '',
-        input_files=[_parser_input(i) for i in (t.inputFiles or [])],
-        output_files=[_parser_output(o) for o in (t.outputFiles or [])],
-        gateway_id=t.gatewayId or '',
-    )
-
-
 # proto-name -> Thrift protocol value maps (mirror grpc_adapters); inverted
 # below to go Thrift value -> proto value, preserving the divergent name pairs
 # (Thrift CLOUD <-> proto JSP_CLOUD; Thrift LOCAL <-> proto
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 661d7a69d..9d1a48e40 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -31,7 +31,7 @@ from airavata.model.appcatalog.groupresourceprofile.ttypes 
import (
     SlurmComputeResourcePreference,
     AwsComputeResourcePreference
 )
-from airavata.model.appcatalog.parser.ttypes import Parser
+from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
 from airavata.model.appcatalog.storageresource.ttypes import (
     StorageResourceDescription
 )
@@ -54,10 +54,6 @@ from airavata.model.status.ttypes import (
     ProcessStatus
 )
 from airavata.model.user.ttypes import UserProfile
-from airavata.model.workspace.ttypes import (
-    Notification,
-    NotificationPriority
-)
 from airavata_django_portal_sdk import (
     experiment_util
 )
@@ -229,6 +225,53 @@ class ProtoIntOrNoneField(serializers.IntegerField):
         return super().to_representation(value)
 
 
+class ProtoEnumIntField(serializers.Field):
+    """Renders a protobuf enum field as the corresponding THRIFT enum integer.
+
+    Many serializers historically rendered an enum as a raw integer (an
+    auto-generated ``IntegerField``, not a name). proto and Thrift assign
+    different integers to the same member, so the adapter bridged by NAME to 
the
+    Thrift integer; this field does the same in one step via the
+    ``proto_to_thrift`` map (proto int -> Thrift int) built by
+    :func:`proto_enum_int_field`. proto-only members and the zero sentinel map 
to
+    ``None`` (these fields were nullable).
+    """
+
+    def __init__(self, proto_to_thrift, thrift_to_proto, **kwargs):
+        self._proto_to_thrift = proto_to_thrift
+        self._thrift_to_proto = thrift_to_proto
+        super().__init__(**kwargs)
+
+    def to_representation(self, value):
+        return self._proto_to_thrift.get(value)
+
+    def to_internal_value(self, data):
+        # Writes pass the Thrift integer; map it back to the proto integer.
+        return self._thrift_to_proto.get(int(data), 0)
+
+
+def proto_enum_int_field(enum_descriptor, thrift_enum, proto_prefix='', 
**kwargs):
+    """Build a :class:`ProtoEnumIntField` bridging a proto enum to the Thrift 
enum
+    integer by member NAME (stripping ``proto_prefix`` from proto-namespaced
+    members). Members absent from the Thrift enum map to ``None``.
+    """
+    proto_to_thrift = {}
+    thrift_to_proto = {}
+    for v in enum_descriptor.values:
+        name = v.name
+        if proto_prefix and name.startswith(proto_prefix):
+            name = name[len(proto_prefix):]
+        thrift_member = getattr(thrift_enum, name, None)
+        if thrift_member is not None:
+            proto_to_thrift[v.number] = int(thrift_member)
+            thrift_to_proto[int(thrift_member)] = v.number
+        else:
+            proto_to_thrift[v.number] = None
+    return ProtoEnumIntField(
+        proto_to_thrift=proto_to_thrift, thrift_to_proto=thrift_to_proto,
+        **kwargs)
+
+
 class StoredJSONField(serializers.JSONField):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -2038,12 +2081,98 @@ class StorageResourceSerializer(
     updateTime = UTCPosixTimestampDateTimeField()
 
 
-class ParserSerializer(thrift_utils.create_serializer_class(Parser)):
+def _parser_pb2():
+    from airavata_sdk.generated.org.apache.airavata.model.appcatalog.parser 
import (
+        parser_pb2,
+    )
+    return parser_pb2
+
+
+class ParserInputSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``ParserInput`` message."""
+
+    id = serializers.CharField(allow_blank=True, allow_null=True, 
required=False)
+    name = serializers.CharField(allow_blank=True, allow_null=True, 
required=False)
+    requiredInput = serializers.BooleanField(
+        source='required_input', required=False, default=False)
+    parserId = serializers.CharField(
+        source='parser_id', allow_blank=True, allow_null=True, required=False)
+    # IOType renders as the Thrift integer (proto FILE=1 -> Thrift FILE=0).
+    type = proto_enum_int_field(
+        _parser_pb2().IOType.DESCRIPTOR, _ThriftIOType, 'IO_TYPE_',
+        required=False, allow_null=True)
+
+
+class ParserOutputSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``ParserOutput`` message."""
+
+    id = serializers.CharField(allow_blank=True, allow_null=True, 
required=False)
+    name = serializers.CharField(allow_blank=True, allow_null=True, 
required=False)
+    requiredOutput = serializers.BooleanField(
+        source='required_output', required=False, default=False)
+    parserId = serializers.CharField(
+        source='parser_id', allow_blank=True, allow_null=True, required=False)
+    type = proto_enum_int_field(
+        _parser_pb2().IOType.DESCRIPTOR, _ThriftIOType, 'IO_TYPE_',
+        required=False, allow_null=True)
+
+
+class ParserSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``Parser`` message."""
+
+    id = serializers.CharField(allow_blank=True, allow_null=True, 
required=False)
+    imageName = serializers.CharField(
+        source='image_name', allow_blank=True, allow_null=True, required=False)
+    outputDirPath = serializers.CharField(
+        source='output_dir_path', allow_blank=True, allow_null=True,
+        required=False)
+    inputDirPath = serializers.CharField(
+        source='input_dir_path', allow_blank=True, allow_null=True,
+        required=False)
+    executionCommand = serializers.CharField(
+        source='execution_command', allow_blank=True, allow_null=True,
+        required=False)
+    inputFiles = ParserInputSerializer(
+        source='input_files', many=True, required=False)
+    outputFiles = ParserOutputSerializer(
+        source='output_files', many=True, required=False)
+    gatewayId = serializers.CharField(
+        source='gateway_id', allow_blank=True, allow_null=True, required=False)
     url = FullyEncodedHyperlinkedIdentityField(
         view_name='django_airavata_api:parser-detail',
         lookup_field='id',
         lookup_url_kwarg='parser_id')
 
+    def create(self, validated_data):
+        pp = _parser_pb2()
+        return pp.Parser(
+            id=validated_data.get('id', '') or '',
+            image_name=validated_data.get('image_name', '') or '',
+            output_dir_path=validated_data.get('output_dir_path', '') or '',
+            input_dir_path=validated_data.get('input_dir_path', '') or '',
+            execution_command=validated_data.get('execution_command', '') or 
'',
+            gateway_id=validated_data.get('gateway_id', '') or '',
+            input_files=[
+                pp.ParserInput(
+                    id=i.get('id', '') or '',
+                    name=i.get('name', '') or '',
+                    required_input=bool(i.get('required_input', False)),
+                    parser_id=i.get('parser_id', '') or '',
+                    type=i.get('type', 0) or 0,
+                ) for i in validated_data.get('input_files', []) or []],
+            output_files=[
+                pp.ParserOutput(
+                    id=o.get('id', '') or '',
+                    name=o.get('name', '') or '',
+                    required_output=bool(o.get('required_output', False)),
+                    parser_id=o.get('parser_id', '') or '',
+                    type=o.get('type', 0) or 0,
+                ) for o in validated_data.get('output_files', []) or []],
+        )
+
+    def update(self, instance, validated_data):
+        return self.create(validated_data)
+
 
 class UserHasWriteAccessToPathSerializer(serializers.Serializer):
     userHasWriteAccess = serializers.SerializerMethodField()
@@ -2227,38 +2356,83 @@ class 
AckNotificationSerializer(serializers.ModelSerializer):
         model = models.User_Notifications
 
 
-class 
NotificationSerializer(thrift_utils.create_serializer_class(Notification)):
+def _notification_workspace_pb2():
+    from airavata_sdk.generated.org.apache.airavata.model.workspace import (
+        workspace_pb2,
+    )
+    return workspace_pb2
+
+
+class NotificationSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``Notification`` message."""
+
+    notificationId = serializers.CharField(
+        source='notification_id', read_only=True)
+    gatewayId = serializers.CharField(
+        source='gateway_id', allow_blank=True, allow_null=True, required=False)
+    title = serializers.CharField(
+        allow_blank=True, allow_null=True, required=False)
+    notificationMessage = serializers.CharField(
+        source='notification_message', allow_blank=True, allow_null=True,
+        required=False)
+    creationTime = ProtoTimestampField(
+        source='creation_time', null_if_zero=True, required=False)
+    publishedTime = ProtoTimestampField(source='published_time', 
required=False)
+    expirationTime = ProtoTimestampField(source='expiration_time', 
required=False)
+    # priority renders as the member NAME (proto LOW/NORMAL/HIGH == Thrift 
names).
+    priority = proto_enum_name_field(
+        _notification_workspace_pb2().NotificationPriority.DESCRIPTOR,
+        proto_prefix='NOTIFICATION_PRIORITY_', required=False)
     url = FullyEncodedHyperlinkedIdentityField(
         view_name='django_airavata_api:manage-notifications-detail',
-        lookup_field='notificationId',
+        lookup_field='notification_id',
         lookup_url_kwarg='notification_id')
-    priority = thrift_utils.ThriftEnumField(NotificationPriority)
-    creationTime = UTCPosixTimestampDateTimeField(allow_null=True)
-    publishedTime = UTCPosixTimestampDateTimeField()
-    expirationTime = UTCPosixTimestampDateTimeField()
     userHasWriteAccess = serializers.SerializerMethodField()
-    showInDashboard = serializers.BooleanField(default=False)
+    showInDashboard = serializers.SerializerMethodField()
 
-    def get_userHasWriteAccess(self, userProfile):
+    def get_userHasWriteAccess(self, notification):
         request = self.context['request']
         return request.is_gateway_admin
 
-    def validate(self, attrs):
-        del attrs["showInDashboard"]
+    def get_showInDashboard(self, notification):
+        extensions = models.NotificationExtension.objects.filter(
+            notification_id=notification.notification_id)
+        return bool(extensions) and extensions[0].showInDashboard
 
+    def validate(self, attrs):
+        attrs.pop("showInDashboard", None)
         return attrs
 
-    def to_representation(self, notification):
-        notification_extension_list = 
models.NotificationExtension.objects.filter(
-            notification_id=notification.notificationId)
-        setattr(notification, "showInDashboard",
-                False if len(notification_extension_list) == 0 else 
notification_extension_list[0].showInDashboard)
+    def create(self, validated_data):
+        w = _notification_workspace_pb2()
+        return w.Notification(
+            gateway_id=validated_data.get('gateway_id', '') or '',
+            title=validated_data.get('title', '') or '',
+            notification_message=validated_data.get(
+                'notification_message', '') or '',
+            creation_time=validated_data.get('creation_time', 0) or 0,
+            published_time=validated_data.get('published_time', 0) or 0,
+            expiration_time=validated_data.get('expiration_time', 0) or 0,
+            priority=validated_data.get('priority', 0) or 0,
+        )
 
-        return super().to_representation(notification)
+    def update(self, instance, validated_data):
+        for proto_field in ('gateway_id', 'title', 'notification_message',
+                            'creation_time', 'published_time', 
'expiration_time',
+                            'priority'):
+            if proto_field in validated_data:
+                value = validated_data[proto_field]
+                if proto_field.endswith('_time') or proto_field == 'priority':
+                    value = value or 0
+                else:
+                    value = value or ''
+                setattr(instance, proto_field, value)
+        return instance
 
     def update_notification_extension(self, request, notification):
         if "showInDashboard" in request.data:
-            existing_entries = 
models.NotificationExtension.objects.filter(notification_id=notification.notificationId)
+            existing_entries = models.NotificationExtension.objects.filter(
+                notification_id=notification.notification_id)
 
             if len(existing_entries) > 0:
                 existing_entries.update(
@@ -2266,7 +2440,7 @@ class 
NotificationSerializer(thrift_utils.create_serializer_class(Notification))
                 )
             else:
                 models.NotificationExtension.objects.create(
-                    notification_id=notification.notificationId,
+                    notification_id=notification.notification_id,
                     showInDashboard=request.data["showInDashboard"]
                 )
 
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 99be9e76e..d018394b6 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1594,25 +1594,20 @@ class ParserViewSet(mixins.CreateModelMixin,
     lookup_field = 'parser_id'
 
     def get_list(self):
-        return [
-            grpc_adapters.parser(p)
-            for p in self.request.airavata.research.list_all_parsers(
-                settings.GATEWAY_ID)
-        ]
+        return list(self.request.airavata.research.list_all_parsers(
+            settings.GATEWAY_ID))
 
     def get_instance(self, lookup_value):
-        return grpc_adapters.parser(
-            self.request.airavata.research.get_parser(
-                lookup_value, settings.GATEWAY_ID))
+        return self.request.airavata.research.get_parser(
+            lookup_value, settings.GATEWAY_ID)
 
     def perform_create(self, serializer):
         parser = serializer.save()
-        parser.id = self.request.airavata.research.save_parser(
-            grpc_requests.parser(parser))
+        parser.id = self.request.airavata.research.save_parser(parser)
 
     def perform_update(self, serializer):
         parser = serializer.save()
-        
self.request.airavata.research.save_parser(grpc_requests.parser(parser))
+        self.request.airavata.research.save_parser(parser)
 
 
 def _user_storage_path(path, experiment_id=None, request=None):
@@ -1816,32 +1811,27 @@ class ManageNotificationViewSet(APIBackedViewSet):
     lookup_field = 'notification_id'
 
     def get_instance(self, lookup_value):
-        return grpc_adapters.notification(
-            self.request.airavata.research.get_notification(
-                settings.GATEWAY_ID, lookup_value))
+        return self.request.airavata.research.get_notification(
+            settings.GATEWAY_ID, lookup_value)
 
     def get_list(self):
-        return [
-            grpc_adapters.notification(n)
-            for n in self.request.airavata.research.get_all_notifications(
-                self.gateway_id)
-        ]
+        return list(self.request.airavata.research.get_all_notifications(
+            self.gateway_id))
 
     def perform_destroy(self, instance):
         self.request.airavata.research.delete_notification(
-            settings.GATEWAY_ID, instance.notificationId)
+            settings.GATEWAY_ID, instance.notification_id)
 
     def perform_create(self, serializer):
-        notification = serializer.save(gatewayId=self.gateway_id)
-        notification.notificationId = 
self.request.airavata.research.create_notification(
-            grpc_requests.notification(notification))
+        notification = serializer.save(gateway_id=self.gateway_id)
+        notification.notification_id = (
+            self.request.airavata.research.create_notification(notification))
 
         serializer.update_notification_extension(self.request, notification)
 
     def perform_update(self, serializer):
         notification = serializer.save()
-        self.request.airavata.research.update_notification(
-            grpc_requests.notification(notification))
+        self.request.airavata.research.update_notification(notification)
 
         serializer.update_notification_extension(self.request, notification)
 
diff --git a/airavata-django-portal/django_airavata/context_processors.py 
b/airavata-django-portal/django_airavata/context_processors.py
index c430dc397..993b0ae74 100644
--- a/airavata-django-portal/django_airavata/context_processors.py
+++ b/airavata-django-portal/django_airavata/context_processors.py
@@ -10,21 +10,39 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.urls import reverse
 
 from django_airavata.app_config import AiravataAppConfig
-from django_airavata.apps.api import grpc_adapters
 from django_airavata.apps.api.models import User_Notifications
 
 logger = logging.getLogger(__name__)
 
+# proto NotificationPriority value -> the Thrift NotificationPriority integer 
the
+# frontend dashboard expects (proto LOW=1/NORMAL=2/HIGH=3 vs Thrift 0/1/2). 
Built
+# lazily so this module stays importable without the gRPC SDK on the path.
+_notification_priority_proto_to_thrift = None
+
+
+def _notification_priority(value):
+    global _notification_priority_proto_to_thrift
+    if _notification_priority_proto_to_thrift is None:
+        from airavata.model.workspace.ttypes import NotificationPriority
+        from airavata_sdk.generated.org.apache.airavata.model.workspace import 
(
+            workspace_pb2,
+        )
+        proto = workspace_pb2.NotificationPriority
+        _notification_priority_proto_to_thrift = {
+            v.number: int(getattr(NotificationPriority, v.name))
+            for v in proto.DESCRIPTOR.values
+            if hasattr(NotificationPriority, v.name)
+        }
+    return _notification_priority_proto_to_thrift.get(value)
+
 
 def get_notifications(request):
     if request.user.is_authenticated and getattr(request, 'authz_token', None):
         unread_notifications = 0
         try:
-            notifications = [
-                grpc_adapters.notification(n)
-                for n in request.airavata.research.get_all_notifications(
-                    settings.GATEWAY_ID)
-            ]
+            notifications = list(
+                request.airavata.research.get_all_notifications(
+                    settings.GATEWAY_ID))
         except Exception:
             logger.warning("Failed to load notifications")
             notifications = []
@@ -32,25 +50,34 @@ def get_notifications(request):
         valid_notifications = []
         for notification in notifications:
 
-            notification_data = notification.__dict__
+            notification_data = {
+                "notificationId": notification.notification_id,
+                "gatewayId": notification.gateway_id,
+                "title": notification.title,
+                "notificationMessage": notification.notification_message,
+                "creationTime": notification.creation_time,
+                "publishedTime": notification.published_time,
+                "expirationTime": notification.expiration_time,
+                "priority": _notification_priority(notification.priority),
+            }
             expirationTime = datetime.datetime.fromtimestamp(
-                notification.expirationTime / 1000)
+                notification.expiration_time / 1000)
             publishedTime = datetime.datetime.fromtimestamp(
-                notification.publishedTime / 1000)
+                notification.published_time / 1000)
 
             if(expirationTime > current_time and publishedTime < current_time):
                 notification_data['url'] = request.build_absolute_uri(
                     reverse('django_airavata_api:ack-notifications'))\
-                    + "?id=" + str(notification.notificationId)
+                    + "?id=" + str(notification.notification_id)
 
                 try:
                     notification_status = User_Notifications.objects.get(
-                        notification_id=notification.notificationId,
+                        notification_id=notification.notification_id,
                         username=request.user.username)
                 except ObjectDoesNotExist:
                     notification_status = User_Notifications.objects.create(
                         username=request.user.username,
-                        notification_id=notification.notificationId)
+                        notification_id=notification.notification_id)
                 notification_data['is_read'] = notification_status.is_read
                 if not notification_status.is_read:
                     unread_notifications += 1

Reply via email to