From 352eda45572c1fda2391180f2b76adc0a3f1ea5f Mon Sep 17 00:00:00 2001 From: KlemenSpruk Date: Mon, 20 Nov 2023 11:02:38 +0100 Subject: [PATCH] saving --- .../celery/background_tasks/base_task.py | 5 +++- .../celery/background_tasks/beat_task.py | 5 ++-- .../background_tasks/notification_tasks.py | 6 ++--- .../notifications/base/channels/channel.py | 7 ++--- .../base/channels/integrations/aws_ses.py | 11 +++----- .../integrations/aws_sns_single_sms.py | 11 +++----- .../base/channels/integrations/nexmo_sms.py | 10 +++---- .../integrations/provider_integration.py | 9 ++++--- .../base/channels/integrations/t2.py | 11 +++----- .../base/channels/mail_channel.py | 2 +- .../notifications/base/enums.py | 2 +- .../notifications/base/notification.py | 19 ++++++++------ ..._mixin.py => send_notification_service.py} | 26 +++++++++++++------ .../notifications/email_notification.py | 3 ++- .../notifications/rest/notification.py | 3 ++- django_project_base/settings.py | 2 +- 16 files changed, 66 insertions(+), 66 deletions(-) rename django_project_base/notifications/base/{send_notification_mixin.py => send_notification_service.py} (88%) diff --git a/django_project_base/celery/background_tasks/base_task.py b/django_project_base/celery/background_tasks/base_task.py index 58ac14c6..95f3ab10 100644 --- a/django_project_base/celery/background_tasks/base_task.py +++ b/django_project_base/celery/background_tasks/base_task.py @@ -23,7 +23,7 @@ class BaseTask(app.Task): settings: Optional[Settings] = None def before_start(self, task_id, args, kwargs): - if (path := self._app.conf.get('django-settings-module')) and len(path): + if (path := self._app.conf.get("django-settings-module")) and len(path): self.settings = Settings(path) db_settings: dict = self.settings.DATABASES["default"] db_settings.setdefault("TIME_ZONE", None) @@ -40,3 +40,6 @@ def on_failure(self, exc, task_id, args, kwargs, einfo): logging.getLogger(__name__).error( f"Exception: {exc} \n\nTask id: {task_id}\n\nArgs: {args}\n\nKwargs: {kwargs}\n\nEInfo: {einfo}" ) + + def run(self, *args, **kwargs): + return None diff --git a/django_project_base/celery/background_tasks/beat_task.py b/django_project_base/celery/background_tasks/beat_task.py index c16f1aab..e3a34524 100644 --- a/django_project_base/celery/background_tasks/beat_task.py +++ b/django_project_base/celery/background_tasks/beat_task.py @@ -5,7 +5,6 @@ from django_project_base.celery.background_tasks.base_task import BaseTask from django_project_base.celery.celery import app from django_project_base.constants import NOTIFICATION_QUEUE_NAME -from django_project_base.notifications.base.send_notification_mixin import SendNotificationMixin from django_project_base.notifications.models import DjangoProjectBaseNotification @@ -28,7 +27,7 @@ def run(self): # SET CONNECTION AND DELETE IT IN EXTRA DATA for notification in DjangoProjectBaseNotification.objects.using(NOTIFICATION_QUEUE_NAME).filter( - send_at__isnull=False, sent_at__isnull=True, send_at__lte=now_ts + send_at__isnull=False, sent_at__isnull=True, send_at__lte=now_ts ): notification.email_fallback = notification.extra_data["mail-fallback"] notification.user = notification.extra_data["user"] @@ -36,7 +35,7 @@ def run(self): notification.sender = notification.extra_data["sender"] print(notification) - # SendNotificationMixin().make_send(notification, notification.extra_data or {}, resend=False) + # SendNotificationService().make_send(notification, notification.extra_data or {}, resend=False) self._clear_in_progress_status() def on_failure(self, exc, task_id, args, kwargs, einfo): diff --git a/django_project_base/celery/background_tasks/notification_tasks.py b/django_project_base/celery/background_tasks/notification_tasks.py index e9042a51..3926cdb7 100644 --- a/django_project_base/celery/background_tasks/notification_tasks.py +++ b/django_project_base/celery/background_tasks/notification_tasks.py @@ -6,7 +6,7 @@ from django_project_base.celery.background_tasks.base_task import BaseTask from django_project_base.celery.celery import app from django_project_base.celery.settings import NOTIFICATION_QUEABLE_HARD_TIME_LIMIT, NOTIFICATION_SEND_PAUSE_SECONDS -from django_project_base.notifications.base.send_notification_mixin import SendNotificationMixin +from django_project_base.notifications.base.send_notification_service import SendNotificationService LAST_MAIL_SENT_CK = "last-notification-was-sent-timestamp" @@ -26,12 +26,12 @@ def run(self, notification: "DjangoProjectBaseNotification", extra_data): # noq time_from_last_sent: float = time.time() - last_sent if last_sent else 0 if time_from_last_sent < NOTIFICATION_SEND_PAUSE_SECONDS: time.sleep(int(NOTIFICATION_SEND_PAUSE_SECONDS - time_from_last_sent)) - SendNotificationMixin().make_send(notification=notification, extra_data=extra_data) + SendNotificationService(settings=self.settings).make_send(notification=notification, extra_data=extra_data) finally: cache.set(LAST_MAIL_SENT_CK, time.time(), timeout=NOTIFICATION_SEND_PAUSE_SECONDS + 1) def on_failure(self, exc, task_id, args, kwargs, einfo): - super().on_failure(exc=exc, task_id=task_id, args=args, kwargs=kwargs) + super().on_failure(exc=exc, task_id=task_id, args=args, kwargs=kwargs, einfo=einfo) send_notification_task = app.register_task(SendNotificationTask()) diff --git a/django_project_base/notifications/base/channels/channel.py b/django_project_base/notifications/base/channels/channel.py index c3208255..bedd7802 100644 --- a/django_project_base/notifications/base/channels/channel.py +++ b/django_project_base/notifications/base/channels/channel.py @@ -79,16 +79,17 @@ def _find_provider( exclude = [] def get_first_provider(val: Union[str, List]): + print("PROVIDER", val, setting_name, settings) if val and isinstance(val, (list, tuple)): prov = next(filter(lambda i: i not in exclude, val), None) return import_string(prov)() if prov else None - return import_string(val)() if val not in exclude else None + return import_string(val)() if val not in exclude and val else None if settings and getattr(settings, setting_name, None): - return get_first_provider(settings.setting_name) - return get_first_provider(getattr(settings, setting_name, "")) + return get_first_provider(getattr(settings, setting_name)) + return get_first_provider(getattr(settings or object(), setting_name, "")) def clean_recipients(self, recipients: List[Recipient]) -> List[Recipient]: return list(set(recipients)) diff --git a/django_project_base/notifications/base/channels/integrations/aws_ses.py b/django_project_base/notifications/base/channels/integrations/aws_ses.py index 13171254..b2314017 100644 --- a/django_project_base/notifications/base/channels/integrations/aws_ses.py +++ b/django_project_base/notifications/base/channels/integrations/aws_ses.py @@ -1,7 +1,7 @@ -from typing import Union +from typing import Union, Optional import boto3 -from django.conf import settings +from django.conf import settings, Settings from rest_framework.status import is_success from django_project_base.notifications.base.channels.channel import Recipient @@ -30,18 +30,13 @@ def validate_send(self, response: dict): def _get_sms_message(self, notification: DjangoProjectBaseNotification) -> Union[dict, str]: return super()._get_sms_message(notification) - def ensure_credentials(self, extra_data): + def ensure_credentials(self, settings: Optional[Settings] = None): if settings and getattr(settings, "TESTING", False): return self.key_id = getattr(settings, "NOTIFICATIONS_AWS_SES_ACCESS_KEY_ID", None) self.access_key = getattr(settings, "NOTIFICATIONS_AWS_SES_SECRET_ACCESS_KEY", None) self.region = getattr(settings, "NOTIFICATIONS_AWS_SES_REGION_NAME", None) self.settings = settings - if extra_data and (stgs := extra_data.get("SETTINGS")): - self.settings = stgs - self.key_id = getattr(stgs, "NOTIFICATIONS_AWS_SES_ACCESS_KEY_ID", None) - self.access_key = getattr(stgs, "NOTIFICATIONS_AWS_SES_SECRET_ACCESS_KEY", None) - self.region = getattr(stgs, "NOTIFICATIONS_AWS_SES_REGION_NAME", None) assert self.key_id, "AWS SES key id required" assert self.access_key, "AWS SES key id access key required" assert self.region, "AWS SES region required" diff --git a/django_project_base/notifications/base/channels/integrations/aws_sns_single_sms.py b/django_project_base/notifications/base/channels/integrations/aws_sns_single_sms.py index 2e7f2f5a..a772f957 100644 --- a/django_project_base/notifications/base/channels/integrations/aws_sns_single_sms.py +++ b/django_project_base/notifications/base/channels/integrations/aws_sns_single_sms.py @@ -1,7 +1,7 @@ -from typing import Union +from typing import Union, Optional import boto3 -from django.conf import settings +from django.conf import settings, Settings from rest_framework.status import is_success from django_project_base.notifications.base.channels.channel import Recipient @@ -17,18 +17,13 @@ class AwsSnsSingleSMS(ProviderIntegration): def __init__(self) -> None: super().__init__(settings=object()) - def ensure_credentials(self, extra_data): + def ensure_credentials(self, settings: Optional[Settings] = None): if settings and getattr(settings, "TESTING", False): return self.key_id = getattr(settings, "NOTIFICATIONS_AWS_SES_ACCESS_KEY_ID", None) self.access_key = getattr(settings, "NOTIFICATIONS_AWS_SES_SECRET_ACCESS_KEY", None) self.region = getattr(settings, "NOTIFICATIONS_AWS_SES_REGION_NAME", None) self.settings = settings - if extra_data and (stgs := extra_data.get("SETTINGS")): - self.settings = stgs - self.key_id = getattr(stgs, "NOTIFICATIONS_AWS_SES_ACCESS_KEY_ID", None) - self.access_key = getattr(stgs, "NOTIFICATIONS_AWS_SES_SECRET_ACCESS_KEY", None) - self.region = getattr(stgs, "NOTIFICATIONS_AWS_SES_REGION_NAME", None) assert self.key_id, "AWS SES key id required" assert self.access_key, "AWS SES key id access key required" assert self.region, "AWS SES region required" diff --git a/django_project_base/notifications/base/channels/integrations/nexmo_sms.py b/django_project_base/notifications/base/channels/integrations/nexmo_sms.py index fd683e1b..0c1c4afb 100644 --- a/django_project_base/notifications/base/channels/integrations/nexmo_sms.py +++ b/django_project_base/notifications/base/channels/integrations/nexmo_sms.py @@ -1,7 +1,7 @@ -from typing import Union +from typing import Union, Optional import requests -from django.conf import settings +from django.conf import settings, Settings from rest_framework.status import is_success from django_project_base.notifications.base.channels.channel import Recipient @@ -17,16 +17,12 @@ class NexmoSMS(ProviderIntegration): def __init__(self) -> None: super().__init__(settings=object()) - def ensure_credentials(self, extra_data): + def ensure_credentials(self, settings: Optional[Settings] = None): if settings and getattr(settings, "TESTING", False): return self.api_key = getattr(settings, "NEXMO_API_KEY", None) self.api_secret = getattr(settings, "NEXMO_API_SECRET", None) self.settings = settings - if extra_data and (stgs := extra_data.get("SETTINGS")): - self.settings = stgs - self.api_key = getattr(stgs, "NEXMO_API_KEY", None) - self.api_secret = getattr(stgs, "NEXMO_API_SECRET", None) assert self.api_key, "NEXMO_API_KEY required" assert self.api_secret, "NEXMO_API_SECRET required" diff --git a/django_project_base/notifications/base/channels/integrations/provider_integration.py b/django_project_base/notifications/base/channels/integrations/provider_integration.py index 3e21f852..b5d77f1f 100644 --- a/django_project_base/notifications/base/channels/integrations/provider_integration.py +++ b/django_project_base/notifications/base/channels/integrations/provider_integration.py @@ -1,9 +1,10 @@ import re from abc import ABC, abstractmethod from html import unescape -from typing import Union +from typing import Union, Optional import swapper +from django.conf import Settings from django.urls import reverse from django.utils.html import strip_tags @@ -12,20 +13,20 @@ class ProviderIntegration(ABC): - settings: object + settings: Settings is_sms_provider = True def __init__(self, settings: object) -> None: super().__init__() - self.settings = settings + self.settings = Settings @abstractmethod def validate_send(self, response: dict): pass @abstractmethod - def ensure_credentials(self, extra_data: dict): + def ensure_credentials(self, settings: Optional[Settings] = None): pass @abstractmethod diff --git a/django_project_base/notifications/base/channels/integrations/t2.py b/django_project_base/notifications/base/channels/integrations/t2.py index d893e12a..8d754c40 100644 --- a/django_project_base/notifications/base/channels/integrations/t2.py +++ b/django_project_base/notifications/base/channels/integrations/t2.py @@ -1,9 +1,9 @@ import json -from typing import Union +from typing import Union, Optional import requests import swapper -from django.conf import settings +from django.conf import settings, Settings from django.contrib.auth import get_user_model from requests.auth import HTTPBasicAuth from rest_framework.status import is_success @@ -277,18 +277,13 @@ class T2(ProviderIntegration): def __init__(self) -> None: super().__init__(settings=object()) - def ensure_credentials(self, extra_data): + def ensure_credentials(self, settings: Optional[Settings] = None): if settings and getattr(settings, "TESTING", False): return self.username = getattr(settings, "NOTIFICATIONS_T2_USERNAME", None) self.password = getattr(settings, "NOTIFICATIONS_T2_PASSWORD", None) self.url = getattr(settings, "NOTIFICATIONS_SMS_API_URL", None) self.settings = settings - if extra_data and (stgs := extra_data.get("SETTINGS")): - self.settings = stgs - self.username = getattr(stgs, "NOTIFICATIONS_T2_USERNAME", None) - self.password = getattr(stgs, "NOTIFICATIONS_T2_PASSWORD", None) - self.url = getattr(stgs, "NOTIFICATIONS_SMS_API_URL", None) assert self.username, "NOTIFICATIONS_T2_USERNAME is required" assert self.password, "NOTIFICATIONS_T2_PASSWORD is required" assert len(self.url) > 0, "NOTIFICATIONS_T2_PASSWORD is required" diff --git a/django_project_base/notifications/base/channels/mail_channel.py b/django_project_base/notifications/base/channels/mail_channel.py index eb2d049b..54153685 100644 --- a/django_project_base/notifications/base/channels/mail_channel.py +++ b/django_project_base/notifications/base/channels/mail_channel.py @@ -17,7 +17,7 @@ class MailChannel(Channel): notification_price = 0.0002 # TODO get from settings - provider_setting_name = "EMAIL_PROVIDER" + provider_setting_name = "NOTIFICATIONS_EMAIL_PROVIDER" def send(self, notification: DjangoProjectBaseNotification, extra_data, **kwargs) -> int: if getattr(settings, "TESTING", False): diff --git a/django_project_base/notifications/base/enums.py b/django_project_base/notifications/base/enums.py index e701c350..68289258 100644 --- a/django_project_base/notifications/base/enums.py +++ b/django_project_base/notifications/base/enums.py @@ -41,7 +41,7 @@ def channel( ) if channel: channel.provider = channel._find_provider(settings=settings, setting_name=channel.provider_setting_name) - channel.provider.ensure_credentials(extra_data=extra_data) + channel.provider.ensure_credentials(settings=settings) channel.provider.ensure_dlr_user(project_slug) if ensure_dlr_user else False return channel diff --git a/django_project_base/notifications/base/notification.py b/django_project_base/notifications/base/notification.py index 7a0ea592..89af3251 100644 --- a/django_project_base/notifications/base/notification.py +++ b/django_project_base/notifications/base/notification.py @@ -4,7 +4,7 @@ from typing import List, Optional, Type import swapper -from django.conf import settings +from django.conf import settings, Settings from django.contrib.auth import get_user_model from django_project_base.constants import ( @@ -16,11 +16,11 @@ from django_project_base.notifications.base.duplicate_notification_mixin import DuplicateNotificationMixin from django_project_base.notifications.base.enums import ChannelIdentifier, NotificationLevel, NotificationType from django_project_base.notifications.base.queable_notification_mixin import QueableNotificationMixin -from django_project_base.notifications.base.send_notification_mixin import SendNotificationMixin +from django_project_base.notifications.base.send_notification_service import SendNotificationService from django_project_base.notifications.models import DjangoProjectBaseMessage, DjangoProjectBaseNotification -class Notification(QueableNotificationMixin, DuplicateNotificationMixin, SendNotificationMixin): +class Notification(QueableNotificationMixin, DuplicateNotificationMixin): _persist = False _delay = None _recipients = [] @@ -112,7 +112,7 @@ def resend(notification: DjangoProjectBaseNotification, user_pk: Optional[str] = ) notification.email_fallback = mail_fallback notification.save(update_fields=["recipients", "recipients_original_payload_search"]) - SendNotificationMixin().make_send(notification, notification.extra_data or {}, resend=True) + SendNotificationService(settings=settings).make_send(notification, notification.extra_data or {}, resend=True) def __set_via_channels(self, val): self._via_channels = val @@ -178,7 +178,7 @@ def send(self) -> DjangoProjectBaseNotification: extra_data=self._extra_data, ) - notification = self._ensure_channels(required_channels, notification) + notification = self._ensure_channels(channels=required_channels, notification=notification, settings=settings) required_channels.sort() if self.persist: @@ -216,7 +216,7 @@ def send(self) -> DjangoProjectBaseNotification: return notification if not self.send_at: - notification = self.make_send(notification, self._extra_data) + SendNotificationService(settings=settings).make_send(notification, self._extra_data, resend=False) else: if not self.persist: raise Exception("Delayed notification must be persisted") @@ -245,14 +245,17 @@ def send(self) -> DjangoProjectBaseNotification: return notification def _ensure_channels( - self, channels: List[str], notification: DjangoProjectBaseNotification + self, + channels: List[str], + notification: DjangoProjectBaseNotification, + settings: Optional[Settings] = None, ) -> DjangoProjectBaseNotification: from django_project_base.notifications.base.channels.mail_channel import MailChannel extra_data = self._extra_data.get("a_extra_data") or self._extra_data for channel_name in channels: # ensure dlr user and check providers - channel = ChannelIdentifier.channel(channel_name, extra_data=extra_data, project_slug=self._project) + channel = ChannelIdentifier.channel(channel_name, settings=settings, project_slug=self._project) if not channel and extra_data.get("is_system_notification"): continue diff --git a/django_project_base/notifications/base/send_notification_mixin.py b/django_project_base/notifications/base/send_notification_service.py similarity index 88% rename from django_project_base/notifications/base/send_notification_mixin.py rename to django_project_base/notifications/base/send_notification_service.py index e964f91c..afd8e524 100644 --- a/django_project_base/notifications/base/send_notification_mixin.py +++ b/django_project_base/notifications/base/send_notification_service.py @@ -4,18 +4,26 @@ from django.core.cache import cache from django.utils import timezone +from django.conf import Settings from django_project_base.constants import NOTIFICATION_QUEUE_NAME from django_project_base.notifications.base.enums import ChannelIdentifier from django_project_base.notifications.models import DjangoProjectBaseNotification -class SendNotificationMixin(object): +class SendNotificationService(object): + settings: Settings + + def __init__(self, settings: Settings) -> None: + super().__init__() + self.settings = settings + def make_send( self, notification: DjangoProjectBaseNotification, extra_data, resend=False ) -> DjangoProjectBaseNotification: + # TODO: THIS SHOULD BE CALLED ONLY FROM CELERY BACKGROUND TASK + sent_channels: list = [] failed_channels: list = [] - dj_settings = getattr(self, "settings", None) exceptions = "" from django_project_base.licensing.logic import LogAccessService @@ -23,8 +31,8 @@ def make_send( if notification.required_channels is None: return notification if ( - dj_settings - and (phn_allowed := getattr(dj_settings, "IS_PHONE_NUMBER_ALLOWED_FUNCTION", "")) + self.settings + and (phn_allowed := getattr(self.settings, "IS_PHONE_NUMBER_ALLOWED_FUNCTION", "")) and phn_allowed ): cache.set("IS_PHONE_NUMBER_ALLOWED_FUNCTION".lower(), phn_allowed, timeout=None) @@ -54,8 +62,12 @@ def make_send( required_channels.add(SmsChannel.name) for channel_identifier in required_channels: + print(self.settings) channel = ChannelIdentifier.channel( - channel_identifier, settings=dj_settings, project_slug=notification.project_slug, ensure_dlr_user=False + channel_identifier, + settings=self.settings, + project_slug=notification.project_slug, + ensure_dlr_user=False, ) try: # check license @@ -130,7 +142,5 @@ def make_send( ], using=NOTIFICATION_QUEUE_NAME, ) - - if dj_settings: - db.connections.close_all() + db.connections.close_all() return notification diff --git a/django_project_base/notifications/email_notification.py b/django_project_base/notifications/email_notification.py index bd9f2f0f..261c7920 100644 --- a/django_project_base/notifications/email_notification.py +++ b/django_project_base/notifications/email_notification.py @@ -3,6 +3,7 @@ from typing import List, Optional, Type from django.core.cache import cache +from django.conf import settings from rest_framework.exceptions import PermissionDenied from django_project_base.notifications.base.channels.channel import Channel @@ -72,7 +73,7 @@ def send(self) -> DjangoProjectBaseNotification: project_slug=self._project, ) - notification = self._ensure_channels([MailChannel.name], notification) + notification = self._ensure_channels([MailChannel.name], notification, settings=settings) if self.handle_similar_notifications(notification=notification): return notification diff --git a/django_project_base/notifications/rest/notification.py b/django_project_base/notifications/rest/notification.py index 3d9f487d..db97277d 100644 --- a/django_project_base/notifications/rest/notification.py +++ b/django_project_base/notifications/rest/notification.py @@ -7,6 +7,7 @@ import swapper from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType +from django.conf import settings from django.core.cache import cache from django.db.models import ForeignKey, QuerySet from django.http import Http404, HttpResponse @@ -492,7 +493,7 @@ def _create_notification(self, serializer): recipients=serializer.validated_data["message_to"], delay=int(datetime.datetime.now().timestamp()), channels=[ - ChannelIdentifier.channel(c, settings=None, project_slug=None).__class__ + ChannelIdentifier.channel(c, settings=settings, project_slug=None).__class__ for c in serializer.validated_data["send_on_channels"] ], persist=True, diff --git a/django_project_base/settings.py b/django_project_base/settings.py index 8b9694d6..2024bf8d 100644 --- a/django_project_base/settings.py +++ b/django_project_base/settings.py @@ -42,7 +42,7 @@ }, {"name": "NOTIFICATION_SENDERS", "default": {}}, { - "name": "EMAIL_PROVIDER", + "name": "NOTIFICATIONS_EMAIL_PROVIDER", "default": "django_project_base.notifications.base.channels.integrations.aws_ses.AwsSes", }, {