diff --git a/frontend/src/components/debug/index.tsx b/frontend/src/components/debug/index.tsx index 1097897ff8bfe539ca1899e9149ca48a07293b2c..a151d73ff3a5802c4f38a2f6df20ce995393d275 100644 --- a/frontend/src/components/debug/index.tsx +++ b/frontend/src/components/debug/index.tsx @@ -82,6 +82,19 @@ export const Debug = () => { }) } + + const testMail = () => { + fetch('/api/ui/v1/testmail/', { + credentials: 'same-origin', + }) + .then((data) => { + console.log(data) + }) + .catch((err) => { + console.log(err) + }) + } + const whoami = () => { fetch('/api/ui/v1/whoami/', { headers: { @@ -151,6 +164,9 @@ export const Debug = () => { <Button type="button" onClick={() => logout()}> LOGOUT </Button> + <Button type="button" onClick={() => testMail()}> + SEND TEST MAIL + </Button> </Stack> <Box sx={{ maxWidth: '30rem' }}> <Table> diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py index c6441013c29e38e9e46e85eeb419ec7c289bda5e..e6eea48ec3a5174b3b171077dea705879f06964f 100644 --- a/gregsite/settings/base.py +++ b/gregsite/settings/base.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ "django_extensions", "django_filters", "mozilla_django_oidc", + "django_q", "greg", "gregui", ] diff --git a/gregsite/settings/dev.py b/gregsite/settings/dev.py index 403f1f10fe320a20fa8d60ae77d9cc80b866cb0f..0305521fb351dcb4632836bbd7d980c2b79193bc 100644 --- a/gregsite/settings/dev.py +++ b/gregsite/settings/dev.py @@ -15,6 +15,17 @@ ORGREG_CLIENT = { "headers": {"X-Gravitee-Api-Key": "bar"}, } +Q_CLUSTER = { + "name": "greg", + "workers": 4, + "timeout": 90, + "retry": 120, + "queue_limit": 50, + "bulk": 10, + "orm": "default", + "sync": True, +} + AUTHENTICATION_BACKENDS = [ "gregui.authentication.auth_backends.DevBackend", # Fake dev backend "django.contrib.auth.backends.ModelBackend", # default diff --git a/gregui/mailutils.py b/gregui/mailutils.py index 30187aaf73fcec8090921f83e5b95a2b36a8eb48..19795179783c9b54b8b60652045159895fa2618b 100644 --- a/gregui/mailutils.py +++ b/gregui/mailutils.py @@ -1,6 +1,6 @@ from django.conf import settings -from django.core.mail import send_mail from django.template.loader import render_to_string +from django_q.tasks import async_task def registration_template(institution, sponsor) -> str: @@ -17,19 +17,25 @@ def confirmation_template(guest) -> str: return render_to_string("sponsor_confirmation.txt", keywords) -def send_registration_mail(mail_to, sponsor) -> int: - return send_mail( - subject="Subject", - message=registration_template(settings.INSTANCE_NAME, sponsor), - from_email=None, - recipient_list=[mail_to], +def send_registration_mail(mail_to, sponsor) -> str: + return async_task( + "django.core.mail.send_mail", + **{ + "subject": "Subject", + "message": registration_template(settings.INSTANCE_NAME, sponsor), + "from_email": None, + "recipient_list": [mail_to], + } ) -def send_confirmation_mail(mail_to, guest) -> int: - return send_mail( - subject="Subject", - message=confirmation_template(guest), - from_email=None, - recipient_list=[mail_to], +def send_confirmation_mail(mail_to, guest) -> str: + return async_task( + "django.core.mail.send_mail", + **{ + "subject": "Subject", + "message": confirmation_template(guest), + "from_email": None, + "recipient_list": [mail_to], + } ) diff --git a/gregui/tests/test_mailutils.py b/gregui/tests/test_mailutils.py index 25391f5d12898a832f6da139a94f77fb47f46e1a..03f30f3de2d4604859dbf5e1945e47a0830faf4e 100644 --- a/gregui/tests/test_mailutils.py +++ b/gregui/tests/test_mailutils.py @@ -1,4 +1,5 @@ from django.core import mail +from django_q.tasks import result import pytest from gregui import mailutils @@ -33,7 +34,8 @@ Your guest, Foo Bar, has completed their registration, please confirm the guest @pytest.mark.django_db def test_registration_mail(): mail.outbox = [] - assert mailutils.send_registration_mail("test@example.no", "Foo") == 1 + task_id = mailutils.send_registration_mail("test@example.no", "Foo") + assert result(task_id) == 1 assert len(mail.outbox) == 1 assert mail.outbox[0].to == ["test@example.no"] @@ -41,6 +43,7 @@ def test_registration_mail(): @pytest.mark.django_db def test_confirmation_mail(): mail.outbox = [] - assert mailutils.send_confirmation_mail("test@example.no", "Foo") == 1 + task_id = mailutils.send_confirmation_mail("test@example.no", "Foo") + assert result(task_id) == 1 assert len(mail.outbox) == 1 assert mail.outbox[0].to == ["test@example.no"] diff --git a/gregui/urls.py b/gregui/urls.py index 56e17df61e6251dd7d569ba4d0a64892b440298c..cc6fa884e2da3bc3a1884dcb83b4382a0f0ac9f9 100644 --- a/gregui/urls.py +++ b/gregui/urls.py @@ -17,6 +17,7 @@ urlpatterns: List[URLResolver] = [ path("api/ui/v1/logout/", views.logout_view, name="api-logout"), path("api/ui/v1/login/", views.login_view, name="api-login"), path("api/ui/v1/session/", views.SessionView.as_view(), name="api-session"), + path("api/ui/v1/testmail/", views.send_test_email, name="api-testmail"), path("api/ui/v1/whoami/", views.WhoAmIView.as_view(), name="api-whoami"), path("api/ui/v1/userinfo/", UserInfoView.as_view()), # type: ignore path("api/ui/v1/ous/", OusView.as_view()), diff --git a/gregui/views.py b/gregui/views.py index 0801eda1a1f60441677b32e47484ec1970e1b1fd..2bf3fc9d8c59fccfc5a75fe5ae29685e34e2a23b 100644 --- a/gregui/views.py +++ b/gregui/views.py @@ -8,6 +8,7 @@ from rest_framework.views import APIView from greg.models import Role, Sponsor from greg.permissions import IsSponsor +from gregui import mailutils from gregui.models import GregUserProfile @@ -37,6 +38,11 @@ def login_view(request): return redirect("/api/ui/v1/whoami/") +def send_test_email(request): + mailutils.send_registration_mail("test@example.no", "Foo Bar") + return JsonResponse({"detail": "Created task to send test mail."}) + + class SessionView(APIView): authentication_classes = [SessionAuthentication, BasicAuthentication] permission_classes = [IsAuthenticated] diff --git a/poetry.lock b/poetry.lock index 0943957de6dd8d05c85dd11b8282f2f0e5d02b79..c44130e8e57d7010ca5e935b877cb28fdfbe6414 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +name = "ansicon" +version = "1.89.0" +description = "Python wrapper for loading Jason Hood's ANSICON" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "appnope" version = "0.1.2" @@ -6,6 +14,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "arrow" +version = "1.2.0" +description = "Better dates & times for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +python-dateutil = ">=2.7.0" + [[package]] name = "asgiref" version = "3.4.1" @@ -86,6 +105,19 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] python2 = ["typed-ast (>=1.4.2)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "blessed" +version = "1.19.0" +description = "Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities." +category = "main" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +jinxed = {version = ">=1.1.0", markers = "platform_system == \"Windows\""} +six = ">=1.9.0" +wcwidth = ">=0.1.4" + [[package]] name = "certifi" version = "2021.5.30" @@ -259,6 +291,40 @@ python-versions = "*" [package.dependencies] django = ">=1.8" +[[package]] +name = "django-picklefield" +version = "3.0.1" +description = "Pickled object field for Django" +category = "main" +optional = false +python-versions = ">=3" + +[package.dependencies] +Django = ">=2.2" + +[package.extras] +tests = ["tox"] + +[[package]] +name = "django-q" +version = "1.3.9" +description = "A multiprocessing distributed task queue for Django" +category = "main" +optional = false +python-versions = ">=3.6.2,<4" + +[package.dependencies] +arrow = ">=1.1.0,<2.0.0" +blessed = ">=1.17.6,<2.0.0" +django = ">=2.2" +django-picklefield = ">=3.0.1,<4.0.0" +redis = ">=3.5.3,<4.0.0" + +[package.extras] +testing = ["hiredis (>=1.0.1,<2.0.0)", "psutil (>=5.7.0,<6.0.0)", "django-redis (>=4.12.1,<5.0.0)", "iron-mq (>=0.9,<0.10)", "boto3 (>=1.14.12,<2.0.0)", "pymongo (>=3.10.1,<4.0.0)", "croniter (>=0.3.34,<0.4.0)"] +rollbar = ["django-q-rollbar (>=0.1)"] +sentry = ["django-q-sentry (>=0.1)"] + [[package]] name = "django-reversion" version = "4.0.0" @@ -483,6 +549,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jinxed" +version = "1.1.0" +description = "Jinxed Terminal Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ansicon = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "josepy" version = "1.9.0" @@ -919,7 +996,7 @@ test = ["coverage", "docutils", "testscenarios (>=0.4)", "testtools"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" @@ -950,6 +1027,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +hiredis = ["hiredis (>=0.1.3)"] + [[package]] name = "regex" version = "2021.8.28" @@ -1140,7 +1228,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -1166,13 +1254,21 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "3733c24aefbb0889d6071468bd42582689c50ce9903937f94b922997fc4a8f23" +content-hash = "b964658143e61764cbb714c40fe6227158d260a426c2722ff84f813421f12d24" [metadata.files] +ansicon = [ + {file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"}, + {file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"}, +] appnope = [ {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] +arrow = [ + {file = "arrow-1.2.0-py3-none-any.whl", hash = "sha256:8fb7d9d3d4bf90e49e734c22fa077bdd0964135c4b8120de2510575a8d1f620c"}, + {file = "arrow-1.2.0.tar.gz", hash = "sha256:16fc29bbd9e425e3eb0fef3018297910a0f4568f21116fc31771e2760a50e074"}, +] asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, @@ -1197,6 +1293,10 @@ black = [ {file = "black-21.8b0-py3-none-any.whl", hash = "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7"}, {file = "black-21.8b0.tar.gz", hash = "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2"}, ] +blessed = [ + {file = "blessed-1.19.0-py2.py3-none-any.whl", hash = "sha256:1f2d462631b2b6d2d4c3c65b54ef79ad87a6ca2dd55255df2f8d739fcc8a1ddb"}, + {file = "blessed-1.19.0.tar.gz", hash = "sha256:4db0f94e5761aea330b528e84a250027ffe996b5a94bf03e502600c9a5ad7a61"}, +] certifi = [ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, @@ -1329,6 +1429,8 @@ cryptography = [ {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, + {file = "cryptography-3.4.8-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c4129fc3fdc0fa8e40861b5ac0c673315b3c902bbdc05fc176764815b43dd1d"}, + {file = "cryptography-3.4.8-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:695104a9223a7239d155d7627ad912953b540929ef97ae0c34c7b8bf30857e89"}, {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, @@ -1365,6 +1467,14 @@ django-log-request-id = [ {file = "django-log-request-id-1.6.0.tar.gz", hash = "sha256:0126f5da0cacc62cf834efb3cf66e4606031d911ccff048da2f88fe2c0bbcbc9"}, {file = "django_log_request_id-1.6.0-py3-none-any.whl", hash = "sha256:c3f7f53b1fc92f62269c247bee14d578daab000a2f7ae70b70ae288b603b5907"}, ] +django-picklefield = [ + {file = "django-picklefield-3.0.1.tar.gz", hash = "sha256:15ccba592ca953b9edf9532e64640329cd47b136b7f8f10f2939caa5f9ce4287"}, + {file = "django_picklefield-3.0.1-py3-none-any.whl", hash = "sha256:3c702a54fde2d322fe5b2f39b8f78d9f655b8f77944ab26f703be6c0ed335a35"}, +] +django-q = [ + {file = "django-q-1.3.9.tar.gz", hash = "sha256:5c6b4d530aa3aabf9c6aa57376da1ca2abf89a1562b77038b7a04e52a4a0a91b"}, + {file = "django_q-1.3.9-py3-none-any.whl", hash = "sha256:1b74ce3a8931990b136903e3a7bc9b07243282a2b5355117246f05ed5d076e68"}, +] django-reversion = [ {file = "django-reversion-4.0.0.tar.gz", hash = "sha256:ad6d714b4b9b824e22b88d47201cc0f74b5c4294c8d4e1f8d7ac7c3631ef3188"}, {file = "django_reversion-4.0.0-py3-none-any.whl", hash = "sha256:f059c654e38c0dd8dccd7f0990aa2f6d9ad22dab55c5e095f9596aeda8079dcd"}, @@ -1433,6 +1543,10 @@ jinja2 = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] +jinxed = [ + {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, + {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, +] josepy = [ {file = "josepy-1.9.0-py2.py3-none-any.whl", hash = "sha256:49798be66a467e7c81f071fe5ff03ac5e37c6d7081933612259028496bb05a68"}, {file = "josepy-1.9.0.tar.gz", hash = "sha256:51cce8d97ced0556aae0ce3161b26d5f0f54bc42c749d3c606edc6d97d9802dc"}, @@ -1773,6 +1887,10 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] +redis = [ + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, +] regex = [ {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, diff --git a/pyproject.toml b/pyproject.toml index de3569b75f655aff15beb71d8916c63c1d8db8ca..9d78cee33085524aa154086a7b0b97686b95f3a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ whitenoise = "*" django-reversion = "*" django-sesame = {extras = ["ua"], version = "^2.4"} mozilla-django-oidc = "^2.0.0" +django-q = "^1.3.9" [tool.poetry.dev-dependencies] Faker = "*"