Skip to content

Commit

Permalink
ICMSLST-2169 - Email Template model and admin screens
Browse files Browse the repository at this point in the history
  • Loading branch information
marcuspp committed Aug 1, 2023
1 parent 47bdb25 commit 144cd1c
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ HAWK_AUTH_KEY="LITE_API_HAWK_KEY"
SEND_LICENCE_TO_CHIEF=False

SET_INACTIVE_APP_TYPES_ACTIVE=False

# To send emails set GOV_NOTIFY_API_KEY to the development API key in the dev vault
GOV_NOTIFY_API_KEY=
3 changes: 2 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@
]

# Email
# EMAIL_BACKEND = "django_ses.SESBackend" TODO ICMLST-1994
GOV_NOTIFY_API_KEY = env.str("GOV_NOTIFY_API_KEY", default="")
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

AWS_SES_ACCESS_KEY_ID = env.str("AWS_SES_ACCESS_KEY_ID", default="")
AWS_SES_SECRET_ACCESS_KEY = env.str("AWS_SES_SECRET_ACCESS_KEY", default="")
AWS_SES_REGION_NAME = "eu-west-1"
Expand Down
2 changes: 2 additions & 0 deletions config/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@
ICMS_V1_REPLICA_USER = ""
ICMS_V1_REPLICA_PASSWORD = ""
ICMS_V1_REPLICA_DSN = ""

GOV_NOTIFY_API_KEY = "fakekey-11111111-1111-1111-1111-111111111111-22222222-2222-2222-222222222222"
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ services:
- HAWK_AUTH_KEY
- SEND_LICENCE_TO_CHIEF
- SET_INACTIVE_APP_TYPES_ACTIVE
- GOV_NOTIFY_API_KEY
# stdin_open: true
# tty: true
ports:
Expand Down Expand Up @@ -102,6 +103,7 @@ services:
- HAWK_AUTH_ID
- HAWK_AUTH_KEY
- SEND_LICENCE_TO_CHIEF
- GOV_NOTIFY_API_KEY
depends_on:
- redis
volumes:
Expand Down
2 changes: 2 additions & 0 deletions pii-ner-exclude.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3164,3 +3164,5 @@ Access Request Approval Response
An Access Request Approval
request\nis
Access Request Approval
GOV_NOTIFY_API_KEY
GOV Notify
1 change: 1 addition & 0 deletions requirements-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ humanize==3.1.0
Jinja2==3.0.3
lxml==4.9.1
mohawk==1.1.0
notifications-python-client==8.0.1
openpyxl==3.0.7
oracledb==1.2.0
phonenumbers==8.12.12
Expand Down
25 changes: 25 additions & 0 deletions web/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db.models import QuerySet
from guardian.admin import GuardedModelAdmin

from web.mail.api import is_valid_template_id
from web.models import (
Commodity,
CommodityGroup,
Expand All @@ -14,6 +16,7 @@
CountryGroup,
DerogationsApplication,
Email,
EmailTemplate,
ExportApplication,
ExportApplicationType,
Exporter,
Expand Down Expand Up @@ -50,6 +53,27 @@ class ExporterAdmin(GuardedModelAdmin):
...


class EmailTemplateForm(forms.ModelForm):
class Meta:
model = EmailTemplate
fields = ["name", "gov_notify_template_id"]

def clean_gov_notify_template_id(self) -> str:
template_id = self.cleaned_data["gov_notify_template_id"]
if not is_valid_template_id(template_id):
raise ValidationError("GOV Notify template not found")
return template_id


class EmailTemplateAdmin(admin.ModelAdmin):
form = EmailTemplateForm
fields = ("name", "gov_notify_template_id")
readonly_fields = ("name",)

def has_delete_permission(self, request, obj=None) -> bool:
return False


admin.site.register(User, UserAdmin)
admin.site.register(CommodityType)
admin.site.register(Commodity)
Expand All @@ -71,6 +95,7 @@ class ExporterAdmin(GuardedModelAdmin):
admin.site.register(SanctionsAndAdhocApplication)
admin.site.register(SanctionsAndAdhocApplicationGoods)
admin.site.register(DerogationsApplication)
admin.site.register(EmailTemplate, EmailTemplateAdmin)


@admin.register(Permission)
Expand Down
Empty file added web/mail/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions web/mail/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from uuid import UUID

from django.conf import settings
from notifications_python_client import NotificationsAPIClient
from notifications_python_client.errors import HTTPError


def get_gov_notify_client() -> NotificationsAPIClient:
return NotificationsAPIClient(settings.GOV_NOTIFY_API_KEY)


def get_template_by_id(template_id: UUID) -> dict:
client = get_gov_notify_client()
try:
return client.get_template(template_id)
except HTTPError as e:
return e.response.json()


def is_valid_template_id(template_id: UUID) -> bool:
response = get_template_by_id(template_id)
gov_notify_template_id = response.get("id", "")
try:
gov_notify_template_id = UUID(gov_notify_template_id)
except ValueError:
return False
return gov_notify_template_id == template_id
6 changes: 6 additions & 0 deletions web/mail/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from web.types import TypedTextChoices


class EmailTypes(TypedTextChoices):
ACCESS_REQUEST = ("ACCESS_REQUEST", "Access Request")
CASE_COMPLETE = ("CASE_COMPLETE", "Case Complete")
11 changes: 11 additions & 0 deletions web/mail/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.db import models

from .constants import EmailTypes


class EmailTemplate(models.Model):
name = models.CharField(max_length=255, unique=True, choices=EmailTypes.choices)
gov_notify_template_id = models.UUIDField()

def __str__(self) -> str:
return self.get_name_display()
16 changes: 16 additions & 0 deletions web/management/commands/utils/add_email_template_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from web.mail.constants import EmailTypes
from web.models import EmailTemplate

templates = [
(EmailTypes.ACCESS_REQUEST, "d8905fee-1f7d-48dc-bc11-aee71c130b3e"),
(EmailTypes.CASE_COMPLETE, "2e03bc8e-1d57-404d-ba53-0fbf00316a4d"), # /PS-IGNORE
]


def add_email_gov_notify_templates():
EmailTemplate.objects.bulk_create(
[
EmailTemplate(name=name, gov_notify_template_id=gov_notify_template_id)
for name, gov_notify_template_id in templates
]
)
2 changes: 2 additions & 0 deletions web/management/commands/utils/load_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
load_country_data,
load_country_group_data,
)
from .add_email_template_data import add_email_gov_notify_templates
from .add_product_legislation_data import add_product_legislation_data
from .add_sanction_data import add_sanction_data
from .add_template_data import (
Expand Down Expand Up @@ -57,3 +58,4 @@ def load_app_test_data():
add_constabulary_data()
add_product_legislation_data()
add_sanction_data()
add_email_gov_notify_templates()
27 changes: 25 additions & 2 deletions web/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.10 on 2023-07-25 15:10
# Generated by Django 4.1.10 on 2023-07-31 08:52

import uuid

Expand All @@ -22,8 +22,8 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("auth", "0012_alter_user_first_name_max_length"),
("contenttypes", "0002_remove_content_type_name"),
]

operations = [
Expand Down Expand Up @@ -1099,6 +1099,29 @@ class Migration(migrations.Migration):
"abstract": False,
},
),
migrations.CreateModel(
name="EmailTemplate",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"name",
models.CharField(
choices=[
("ACCESS_REQUEST", "Access Request"),
("CASE_COMPLETE", "Case Complete"),
],
max_length=255,
unique=True,
),
),
("gov_notify_template_id", models.UUIDField()),
],
),
migrations.CreateModel(
name="EndorsementImportApplication",
fields=[
Expand Down
4 changes: 2 additions & 2 deletions web/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
from web.domains.template.models import CFSScheduleParagraph, Template
from web.domains.user.models import Email, PhoneNumber, User
from web.flow.models import Process, Task
from web.mail.models import EmailTemplate
from web.models.models import CaseReference, GlobalPermission

__all__ = [
Expand Down Expand Up @@ -267,13 +268,12 @@
"SIGLTransmission",
"CFSScheduleParagraph",
"Template",
"AlternativeEmail",
"Email",
"PersonalEmail",
"PhoneNumber",
"User",
"Process",
"Task",
"CaseReference",
"GlobalPermission",
"EmailTemplate",
]
57 changes: 57 additions & 0 deletions web/tests/mail/test_admin_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from unittest import mock
from uuid import UUID

import pytest

from web.admin import EmailTemplateForm
from web.mail.constants import EmailTypes
from web.models import EmailTemplate

FAKE_TEMPLATE_UUID = UUID("646bea34-20ef-437c-b001-ea557f3ba1e6")


@pytest.mark.django_db
def test_email_template_form_uuid_error():
case_complete = EmailTemplate.objects.get(name=EmailTypes.CASE_COMPLETE)
form = EmailTemplateForm(
instance=case_complete,
data={"gov_notify_template_id": "hello", "name": EmailTypes.CASE_COMPLETE},
)
assert form.is_valid() is False
assert form.errors == {"gov_notify_template_id": ["Enter a valid UUID."]}


@pytest.mark.django_db
@mock.patch("web.admin.is_valid_template_id")
def test_email_template_id_is_invalid(mock_is_valid_template_id):
mock_is_valid_template_id.return_value = False
case_complete = EmailTemplate.objects.get(name=EmailTypes.CASE_COMPLETE)
form = EmailTemplateForm(
instance=case_complete,
data={
"gov_notify_template_id": FAKE_TEMPLATE_UUID,
"name": EmailTypes.CASE_COMPLETE,
},
)
assert form.is_valid() is False
assert form.errors == {"gov_notify_template_id": ["GOV Notify template not found"]}
mock_is_valid_template_id.assert_called_once_with(FAKE_TEMPLATE_UUID)


@pytest.mark.django_db
@mock.patch("web.admin.is_valid_template_id")
def test_email_template_id_is_valid(mock_is_valid_template_id):
mock_is_valid_template_id.return_value = True
case_complete = EmailTemplate.objects.get(name=EmailTypes.CASE_COMPLETE)
form = EmailTemplateForm(
instance=case_complete,
data={
"gov_notify_template_id": FAKE_TEMPLATE_UUID,
"name": EmailTypes.CASE_COMPLETE,
},
)
assert form.is_valid() is True, form.errors
instance = form.save()
mock_is_valid_template_id.assert_called_once_with(FAKE_TEMPLATE_UUID)
assert str(instance) == EmailTypes.CASE_COMPLETE.label
assert instance.gov_notify_template_id == FAKE_TEMPLATE_UUID
53 changes: 53 additions & 0 deletions web/tests/mail/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from unittest import mock
from uuid import UUID

import pytest
from notifications_python_client.errors import HTTPError

from web.mail import api

HTTP_404_NOT_FOUND_ERROR = {
"errors": [{"error": "NoResultFound", "message": "No result found"}],
"status_code": 404,
}


@pytest.mark.parametrize(
"template_id,expected_result,mock_get_template_id_response",
[
(UUID("4adda435-af30-4f24-98a4-1f07e222369e"), False, HTTP_404_NOT_FOUND_ERROR),
(
UUID("646bea34-20ef-437c-b001-ea557f3ba1e6"),
True,
{"id": "646bea34-20ef-437c-b001-ea557f3ba1e6"},
),
(UUID("646bea34-20ef-437c-b001-ea557f3ba1e6"), False, {}),
(
UUID("646bea34-20ef-437c-b001-ea557f3ba1e6"),
False,
{"id": "fb9a1023-3901-44e8-a7d3-a0e309e93951"},
),
],
)
def test_is_valid_template_by_id(template_id, expected_result, mock_get_template_id_response):
with mock.patch("web.mail.api.get_template_by_id") as mock_get_template_id:
mock_get_template_id.return_value = mock_get_template_id_response
assert api.is_valid_template_id(template_id) == expected_result


@mock.patch("notifications_python_client.NotificationsAPIClient.get_template")
def test_get_template_by_id(mock_gov_notify_get_template):
fake_response = {"id": "fb9a1023-3901-44e8-a7d3-a0e309e93951"}
mock_gov_notify_get_template.return_value = fake_response
assert api.get_template_by_id(UUID("fb9a1023-3901-44e8-a7d3-a0e309e93951")) == fake_response


@mock.patch("notifications_python_client.NotificationsAPIClient.get_template")
def test_get_template_by_id_error(mock_gov_notify_get_template):
fake_response = mock.Mock(status_code=404, json=lambda: HTTP_404_NOT_FOUND_ERROR)
fake_error = mock.Mock(response=fake_response)
mock_gov_notify_get_template.side_effect = HTTPError.create(fake_error)
assert (
api.get_template_by_id(UUID("fb9a1023-3901-44e8-a7d3-a0e309e93951"))
== HTTP_404_NOT_FOUND_ERROR
)

0 comments on commit 144cd1c

Please sign in to comment.