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