Skip to content

Commit

Permalink
tox fix and working tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KlemenSpruk committed Nov 20, 2023
1 parent 043a3b4 commit bb24e61
Show file tree
Hide file tree
Showing 21 changed files with 98 additions and 86 deletions.
4 changes: 2 additions & 2 deletions django_project_base/celery/celery.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import os

from celery import Celery, bootsteps
from celery import bootsteps, Celery
from click import Option
from django.apps import apps
from django.utils.crypto import get_random_string
from kombu import Exchange, Queue
from click import Option
from kombu.entity import TRANSIENT_DELIVERY_MODE

from django_project_base.celery.settings import NOTIFICATIONS_QUEUE_VISIBILITY_TIMEOUT
Expand Down
25 changes: 20 additions & 5 deletions django_project_base/notifications/base/channels/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ class Recipient:
email: str
unique_attribute: str

def __init__(self, identifier: str, phone_number: str, email: str, unique_attribute: str = "identifier") -> None:
def __init__(
self,
identifier: str,
phone_number: str,
email: str,
unique_attribute: str = "identifier",
phone_number_validator=None,
) -> None:
super().__init__()
self.identifier = identifier
self.phone_number = (
next(
iter(
PhoneNumberParser.valid_phone_numbers([phone_number]) if phone_number and len(phone_number) else ""
PhoneNumberParser.valid_phone_numbers([phone_number], phone_number_validator)
if phone_number and len(phone_number)
else ""
),
None,
)
Expand Down Expand Up @@ -79,7 +88,6 @@ 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)

Expand Down Expand Up @@ -113,7 +121,9 @@ def create_delivery_report(
)

@abstractmethod
def get_recipients(self, notification: DjangoProjectBaseNotification, unique_identifier="email") -> List[Recipient]:
def get_recipients(
self, notification: DjangoProjectBaseNotification, unique_identifier="email", phone_number_validator=None
) -> List[Recipient]:
rec_obj = notification.email_list
if not rec_obj:
rec_obj = notification.recipients_list
Expand All @@ -131,6 +141,7 @@ def get_recipients(self, notification: DjangoProjectBaseNotification, unique_ide
email=u.get("email", "") or "",
phone_number=u.get("phone_number", "") or "",
unique_attribute=unique_identifier,
phone_number_validator=phone_number_validator,
)
for u in rec_obj
]
Expand Down Expand Up @@ -177,7 +188,9 @@ def send(self, notification: DjangoProjectBaseNotification, extra_data, settings
try:
message = self.provider.get_message(notification)

recipients = self.get_recipients(notification)
recipients = self.get_recipients(
notification, phone_number_validator=getattr(settings, "IS_PHONE_NUMBER_ALLOWED_FUNCTION", None)
)

if not recipients:
raise ValueError("No valid recipients")
Expand Down Expand Up @@ -222,7 +235,9 @@ def send(self, notification: DjangoProjectBaseNotification, extra_data, settings
a_sender=notification.sender,
a_extra_data=extra_data,
a_recipients_list=notification.recipients_list,
a_settings=settings,
delay=int(datetime.datetime.now().timestamp()),
user=extra_data["user"],
).send()
self.create_delivery_report(
notification, recipient, dlr__uuid, auxiliary_notification=a_notification.pk
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Union, Optional
from typing import Optional, Union

import boto3
from django.conf import settings, Settings
from django.conf import Settings
from rest_framework.status import is_success

from django_project_base.notifications.base.channels.channel import Recipient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Union, Optional
from typing import Optional, Union

import boto3
from django.conf import settings, Settings
from django.conf import Settings
from rest_framework.status import is_success

from django_project_base.notifications.base.channels.channel import Recipient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Union, Optional
from typing import Optional, Union

import requests
from django.conf import settings, Settings
from django.conf import Settings
from rest_framework.status import is_success

from django_project_base.notifications.base.channels.channel import Recipient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from abc import ABC, abstractmethod
from html import unescape
from typing import Union, Optional
from typing import Optional, Union

import swapper
from django.conf import Settings
Expand All @@ -19,7 +19,7 @@ class ProviderIntegration(ABC):

def __init__(self, settings: object) -> None:
super().__init__()
self.settings = Settings
self.settings = settings

@abstractmethod
def validate_send(self, response: dict):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json
from typing import Union, Optional
from typing import Optional, Union

import requests
import swapper
from django.conf import settings, Settings
from django.conf import Settings
from django.contrib.auth import get_user_model
from requests.auth import HTTPBasicAuth
from rest_framework.status import is_success
Expand Down
14 changes: 11 additions & 3 deletions django_project_base/notifications/base/channels/mail_channel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uuid
from typing import List

from django.conf import settings, Settings
from django.conf import Settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email

Expand Down Expand Up @@ -32,8 +32,16 @@ def send(self, notification: DjangoProjectBaseNotification, extra_data, settings
)
return super().send(notification=notification, extra_data=extra_data, settings=settings)

def get_recipients(self, notification: DjangoProjectBaseNotification, unique_identifier=""):
return list(set(super().get_recipients(notification, unique_identifier="email")))
def get_recipients(
self, notification: DjangoProjectBaseNotification, unique_identifier="", phone_number_validator=None
):
return list(
set(
super().get_recipients(
notification, unique_identifier="email", phone_number_validator=phone_number_validator
)
)
)

def clean_email_recipients(self, recipients: List[Recipient]) -> List[Recipient]:
valid = []
Expand Down
13 changes: 11 additions & 2 deletions django_project_base/notifications/base/channels/sms_channel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List

from django.conf import Settings

from django_project_base.notifications.base.channels.channel import Channel, Recipient
from django_project_base.notifications.base.enums import ChannelIdentifier
from django_project_base.notifications.models import DjangoProjectBaseNotification
Expand All @@ -15,8 +16,16 @@ class SmsChannel(Channel):

provider_setting_name = "NOTIFICATIONS_SMS_PROVIDER"

def get_recipients(self, notification: DjangoProjectBaseNotification, unique_identifier=""):
return list(set(super().get_recipients(notification, unique_identifier="phone_number")))
def get_recipients(
self, notification: DjangoProjectBaseNotification, unique_identifier="", phone_number_validator=None
):
return list(
set(
super().get_recipients(
notification, unique_identifier="phone_number", phone_number_validator=phone_number_validator
)
)
)

def send(
self, notification: DjangoProjectBaseNotification, extra_data: dict, settings: Settings, **kwargs
Expand Down
56 changes: 24 additions & 32 deletions django_project_base/notifications/base/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ def __init__(

@staticmethod
def resend(notification: DjangoProjectBaseNotification, user_pk: Optional[str] = None):
notification.user = user_pk or (
notification.extra_data or {}
) # TODO: refactor this to read from @prop which always returns {}
notification.user = user_pk
from django_project_base.notifications.rest.notification import MessageToListField

recipients: List[str] = MessageToListField.parse(json.loads(notification.recipients_original_payload))
Expand All @@ -112,7 +110,9 @@ def resend(notification: DjangoProjectBaseNotification, user_pk: Optional[str] =
)
notification.email_fallback = mail_fallback
notification.save(update_fields=["recipients", "recipients_original_payload_search"])
SendNotificationService(settings=settings).make_send(notification, notification.extra_data or {}, resend=True)
SendNotificationService(settings=settings, use_default_db_connection=True).make_send(
notification, {}, resend=True
)

def __set_via_channels(self, val):
self._via_channels = val
Expand Down Expand Up @@ -175,7 +175,6 @@ def send(self) -> DjangoProjectBaseNotification:
send_notification_sms=self.send_notification_sms,
send_notification_sms_text=None,
send_at=self.send_at,
extra_data=self._extra_data,
)

notification = self._ensure_channels(channels=required_channels, notification=notification, settings=settings)
Expand Down Expand Up @@ -210,40 +209,32 @@ def send(self) -> DjangoProjectBaseNotification:
)
notification.recipients_list = rec_list
ext_data = self._extra_data.get("a_extra_data") or self._extra_data

notification.save(update_fields=["extra_data"])
self.enqueue_notification(notification, extra_data=ext_data)
return notification

if not self.send_at:
SendNotificationService(settings=settings, use_default_db_connection=True).make_send(
notification, self._extra_data, resend=False
)
else:
if not self.persist:
raise Exception("Delayed notification must be persisted")
mail_fallback: bool = (
swapper.load_model("django_project_base", "ProjectSettings")
.objects.get(name=USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUMBER, project__slug=notification.project_slug)
.python_value
if notification.project_slug
else False
)
notification.extra_data["mail-fallback"] = mail_fallback
rec_list = []
for usr in self._recipients:
rec_list.append(
{
k: v
for k, v in get_user_model().objects.get(pk=usr).userprofile.__dict__.items()
if not k.startswith("_") and isinstance(v, (str, list, tuple, int, float))
}
)
notification.extra_data["recipients-list"] = rec_list
notification.extra_data["sender"] = notification.sender

notification.save(update_fields=["extra_data"])

# else:
# if not self.persist:
# raise Exception("Delayed notification must be persisted")
# mail_fallback: bool = (
# swapper.load_model("django_project_base", "ProjectSettings")
# .objects.get(name=USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUMBER, project__slug=notification.project_slug)
# .python_value
# if notification.project_slug
# else False
# )
# rec_list = []
# for usr in self._recipients:
# rec_list.append(
# {
# k: v
# for k, v in get_user_model().objects.get(pk=usr).userprofile.__dict__.items()
# if not k.startswith("_") and isinstance(v, (str, list, tuple, int, float))
# }
# )
return notification

def _ensure_channels(
Expand All @@ -255,6 +246,7 @@ def _ensure_channels(
from django_project_base.notifications.base.channels.mail_channel import MailChannel

extra_data = self._extra_data.get("a_extra_data") or self._extra_data
settings = self._extra_data.get("a_settings") or settings
for channel_name in channels:
# ensure dlr user and check providers
channel = ChannelIdentifier.channel(channel_name, settings=settings, project_slug=self._project)
Expand Down
14 changes: 5 additions & 9 deletions django_project_base/notifications/base/phone_number_parser.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from typing import List

from django.core.cache import cache


class PhoneNumberParser:
@staticmethod
def is_allowed(phone_number: str) -> bool:
if (allowed_function := cache.get("IS_PHONE_NUMBER_ALLOWED_FUNCTION".lower(), "")) and allowed_function:
from dill import loads as dloads

return dloads(allowed_function)(phone_number)
def is_allowed(phone_number: str, allowed_validator=None) -> bool:
if allowed_validator:
return allowed_validator(phone_number)
return phone_number and len(phone_number) >= 8

@staticmethod
def valid_phone_numbers(candidates: List[str]) -> List[str]:
def valid_phone_numbers(candidates: List[str], allowed_validator=None) -> List[str]:
valid: List[str] = []
for number in candidates:
if not PhoneNumberParser.is_allowed(number):
if not PhoneNumberParser.is_allowed(number, allowed_validator):
continue
if number.startswith("+"):
valid.append(number.lstrip("+"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging

from django import db
from django.core.cache import cache
from django.conf import Settings
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
Expand Down Expand Up @@ -33,13 +32,6 @@ def make_send(

if notification.required_channels is None:
return notification
if (
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)

already_sent_channels = set(
filter(
lambda i: i not in (None, "") and i,
Expand All @@ -65,7 +57,6 @@ def make_send(
required_channels.add(SmsChannel.name)

for channel_identifier in required_channels:
print(self.settings)
channel = ChannelIdentifier.channel(
channel_identifier,
settings=self.settings,
Expand Down
2 changes: 1 addition & 1 deletion django_project_base/notifications/email_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import uuid
from typing import List, Optional, Type

from django.core.cache import cache
from django.conf import settings
from django.core.cache import cache
from rest_framework.exceptions import PermissionDenied

from django_project_base.notifications.base.channels.channel import Channel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,4 @@ class Migration(migrations.Migration):
name="send_at",
field=models.BigIntegerField(null=True),
),
migrations.AddField(
model_name="djangoprojectbasenotification",
name="extra_data",
field=models.JSONField(null=True),
),
]
1 change: 0 additions & 1 deletion django_project_base/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ class AbstractDjangoProjectBaseNotification(models.Model):
send_notification_sms = models.BooleanField(null=False, blank=False, default=False)
send_notification_sms_text = models.TextField(blank=False, null=True)
send_at = models.BigIntegerField(blank=False, null=True)
extra_data = models.JSONField(null=True, blank=False)

class Meta:
abstract = True
Expand Down
Loading

0 comments on commit bb24e61

Please sign in to comment.