Skip to content

Commit

Permalink
send email if user has no phone number
Browse files Browse the repository at this point in the history
  • Loading branch information
KlemenSpruk committed Oct 26, 2023
1 parent 5e24d36 commit d69be3a
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 20 deletions.
2 changes: 2 additions & 0 deletions django_project_base/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
INVITE_NOTIFICATION_TEXT = "invite-notification-link-text"

NOTIFY_NEW_USER_SETTING_NAME = "notify-new-user-via-email-account-created"

USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER = "notify-user-via-email-if-no-phone-number"
2 changes: 1 addition & 1 deletion django_project_base/licensing/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def log(
for used_channel in list(set(list(items.keys()))):
used += chl_prices.get(used_channel, 0) * items.get(used_channel, 0)

if not kwargs.get("is_system_notification") and used >= 0: # janez medja
if not kwargs.get("is_system_notification") and used >= 0:
raise PermissionDenied(gettext("Your license is consumed. Please contact support."))

if on_sucess:
Expand Down
67 changes: 63 additions & 4 deletions django_project_base/notifications/base/channels/channel.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import datetime
import logging
import uuid
from abc import ABC, abstractmethod
from typing import List, Optional, Union

import swapper
from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.module_loading import import_string

from django_project_base.constants import USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER
from django_project_base.notifications.base.channels.integrations.provider_integration import ProviderIntegration
from django_project_base.notifications.base.phone_number_parser import PhoneNumberParser
from django_project_base.notifications.models import DeliveryReport, DjangoProjectBaseNotification
from django_project_base.notifications.models import (
DeliveryReport,
DjangoProjectBaseNotification,
DjangoProjectBaseMessage,
)
from django_project_base.utils import get_pk_name


Expand All @@ -33,6 +40,7 @@ def __init__(self, identifier: str, phone_number: str, email: str, unique_attrib
)
self.email = email
self.unique_attribute = unique_attribute
self.phone_number = None

def __eq__(self, __o: "Recipient") -> bool:
return str(getattr(self, self.unique_attribute)) == str(getattr(__o, self.unique_attribute))
Expand Down Expand Up @@ -88,14 +96,21 @@ def clean_recipients(self, recipients: List[Recipient]) -> List[Recipient]:
return list(set(recipients))

def create_delivery_report(
self, notification: DjangoProjectBaseNotification, recipient: Recipient, pk: str
self,
notification: DjangoProjectBaseNotification,
recipient: Recipient,
pk: str,
channel: Optional[str] = None,
provider: Optional[str] = None,
auxiliary_notification: Optional[uuid.UUID] = None,
) -> DeliveryReport:
return DeliveryReport.objects.create(
notification=notification,
user_id=recipient.identifier,
channel=f"{self.__module__}.{self.__class__.__name__}",
provider=f"{self.provider.__module__}.{self.provider.__class__.__name__}",
channel=f"{self.__module__}.{self.__class__.__name__}" if not channel else channel,
provider=f"{self.provider.__module__}.{self.provider.__class__.__name__}" if not provider else provider,
pk=pk,
auxiliary_notification=auxiliary_notification,
)

@abstractmethod
Expand Down Expand Up @@ -151,9 +166,53 @@ def make_send(notification_obj, rec_obj, message_str, dlr_pk) -> Optional[Delive
logger.exception(de)
return dlr_obj

from django_project_base.notifications.base.channels.mail_channel import MailChannel

mail_fallback = (
MailChannel.name not in (notification.required_channels or "").split(",")
and notification.email_fallback
)

from django_project_base.notifications.email_notification import EMailNotification
from django_project_base.notifications.base.enums import ChannelIdentifier

for recipient in recipients: # noqa: E203
dlr__uuid = str(uuid.uuid4())
try:
if (
self.provider.is_sms_provider
and not recipient.phone_number
and mail_fallback
and not notification.send_notification_sms
):
try:
a_notification = EMailNotification(
message=DjangoProjectBaseMessage(
subject=notification.message.subject,
body=notification.message.body,
footer=notification.message.footer,
content_type=notification.message.content_type,
),
raw_recipents=[
recipient.identifier,
],
project=notification.project_slug if notification.project_slug else None,
recipients=[
recipient.identifier,
],
a_sender=notification.sender,
a_extra_data=extra_data,
a_recipients_list=notification.recipients_list,
delay=int(datetime.datetime.now().timestamp()),
).send()
self.create_delivery_report(
notification, recipient, dlr__uuid, auxiliary_notification=a_notification.pk
)
continue
except Exception as e:
logger.exception(e)
continue

while dlr := not make_send(
notification_obj=notification, message_str=message, rec_obj=recipient, dlr_pk=dlr__uuid
):
Expand Down
57 changes: 42 additions & 15 deletions django_project_base/notifications/base/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from django.conf import settings
from django.contrib.auth import get_user_model

from django_project_base.constants import EMAIL_SENDER_ID_SETTING_NAME, SMS_SENDER_ID_SETTING_NAME
from django_project_base.constants import (
EMAIL_SENDER_ID_SETTING_NAME,
SMS_SENDER_ID_SETTING_NAME,
USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER,
)
from django_project_base.notifications.base.channels.channel import Channel
from django_project_base.notifications.base.duplicate_notification_mixin import DuplicateNotificationMixin
from django_project_base.notifications.base.enums import ChannelIdentifier, NotificationLevel, NotificationType
Expand Down Expand Up @@ -94,6 +98,14 @@ def resend(notification: DjangoProjectBaseNotification, user_pk: str):
notification.recipients = ",".join(map(str, recipients)) if recipients else None
notification.recipients_original_payload_search = None
notification.sender = Notification._get_sender_config(notification.project_slug)
mail_fallback: bool = (
swapper.load_model("django_project_base", "ProjectSettings")
.objects.get(name=USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER, project__slug=notification.project_slug)
.python_value
if notification.project_slug
else False
)
notification.email_fallback = mail_fallback
notification.save(update_fields=["recipients", "recipients_original_payload_search"])
SendNotificationMixin().make_send(notification, {})

Expand Down Expand Up @@ -176,17 +188,19 @@ def send(self) -> DjangoProjectBaseNotification:
if not self.persist:
raise Exception("Delayed notification must be persisted")
self._set_db()
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("_")
}
)

rec_list = self._extra_data.get("a_recipients_list") or []
if len(rec_list) == 0:
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("_")
}
)
notification.recipients_list = rec_list
self.enqueue_notification(notification, self._extra_data)
self.enqueue_notification(notification, self._extra_data.get("a_extra_data") or self._extra_data)
return notification

notification = self.make_send(notification, self._extra_data)
Expand All @@ -210,23 +224,36 @@ def _ensure_channels(
) -> 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=self._extra_data, project_slug=self._project)
channel = ChannelIdentifier.channel(channel_name, extra_data=extra_data, project_slug=self._project)

if not channel and self._extra_data.get("is_system_notification"):
if not channel and extra_data.get("is_system_notification"):
continue

assert channel

if self.send_notification_sms and channel.name == MailChannel.name:
notification.send_notification_sms_text = channel.provider.get_send_notification_sms_text(
notification=notification, host_url=self._extra_data.get("host_url", "") # noqa: E126
notification=notification, host_url=extra_data.get("host_url", "") # noqa: E126
)

notification.user = self._user

notification.sender = Notification._get_sender_config(self._project)
notification.sender = self._extra_data.get("a_sender") or Notification._get_sender_config(self._project)

mail_fallback = False
if not self._extra_data.get("a_sender"):
mail_fallback: bool = (
swapper.load_model("django_project_base", "ProjectSettings")
.objects.get(name=USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER, project__slug=notification.project_slug)
.python_value
if notification.project_slug
else False
)
notification.email_fallback = mail_fallback

if self._extra_data.get("is_system_notification"):
notification.sender[MailChannel.name] = getattr(settings, "SYSTEM_EMAIL_SENDER_ID", "")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.4 on 2023-10-26 05:55
from gettext import gettext

import swapper
from django.db import migrations

from django_project_base.constants import USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER


def forwards_func(apps, schema_editor):
project_sett = swapper.load_model("django_project_base", "ProjectSettings")
for project in swapper.load_model("django_project_base", "Project").objects.all():
project_sett.objects.get_or_create(
project=project,
name=USE_EMAIL_IF_RECIPIENT_HAS_NO_PHONE_NUBER,
defaults=dict(
description=gettext("Send notification via EMail if user has no phone number"),
value=False,
value_type="bool",
),
)


def reverse_func(apps, schema_editor):
pass


class Migration(migrations.Migration):
dependencies = [
("notifications", "0006_djangoprojectbasenotification_send_notification_sms"),
]

operations = [
migrations.RunPython(forwards_func, reverse_func),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.4 on 2023-10-26 06:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("notifications", "0007_auto_20231026_0555"),
]

operations = [
migrations.AddField(
model_name="deliveryreport",
name="auxiliary_notification",
field=models.UUIDField(null=True, verbose_name="Auxiliary notification"),
),
]
10 changes: 10 additions & 0 deletions django_project_base/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class DjangoProjectBaseNotification(AbstractDjangoProjectBaseNotification):
_user = None
_sender = {}
_email_list = []
_email_fallback = False

def _get_recipients(self):
return self._recipients_list
Expand All @@ -117,6 +118,12 @@ def _get_email_list(self):
def _set_email_list(self, val):
self._email_list = val

def _get_email_fallback(self):
return self._email_fallback

def _set_email_fallback(self, val):
self._email_fallback = val

recipients_list = property(_get_recipients, _set_recipents)

user = property(_get_user, _set_user)
Expand All @@ -125,6 +132,8 @@ def _set_email_list(self, val):

email_list = property(_get_email_list, _set_email_list)

email_fallback = property(_get_email_fallback, _set_email_fallback)


class SearchItemObject:
label = ""
Expand Down Expand Up @@ -245,3 +254,4 @@ class Status(IntDescribedEnum):
status = models.IntegerField(
default=Status.PENDING_DELIVERY, choices=Status.get_choices_tuple(), db_index=True, null=False
)
auxiliary_notification = models.UUIDField(verbose_name=_("Auxiliary notification"), null=True, blank=False)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.4 on 2023-10-26 06:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("demo_django_base", "0009_projectsettings_pending_value"),
]

operations = [
migrations.AlterField(
model_name="projectsettings",
name="action_required",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name="projectsettings",
name="pending_value",
field=models.CharField(
blank=True, max_length=320, null=True, verbose_name="Pending value"
),
),
]

0 comments on commit d69be3a

Please sign in to comment.