From aeae55a05b23db681b9da5c55c1c103fc0cba6d0 Mon Sep 17 00:00:00 2001 From: Moe Date: Mon, 1 Apr 2024 00:39:47 +0300 Subject: [PATCH 01/13] feat: Add django-post_office for mail queueing (#359) * feat: Use django-post_office for mail queueing * Override the base setting in dev setting. --------- Co-authored-by: Tim Schilling --- indymeet/settings/base.py | 9 + indymeet/settings/dev.py | 2 +- indymeet/settings/production.py | 1 - requirements/requirements-dev.txt | 293 +++++++++++++++-------------- requirements/requirements-test.txt | 143 +++++++------- requirements/requirements.in | 1 + requirements/requirements.txt | 46 +++-- supervisord.conf | 8 + 8 files changed, 269 insertions(+), 234 deletions(-) diff --git a/indymeet/settings/base.py b/indymeet/settings/base.py index b17d5e4a..0f7b1029 100644 --- a/indymeet/settings/base.py +++ b/indymeet/settings/base.py @@ -66,6 +66,7 @@ "tailwind", "theme", "widget_tweaks", + "post_office", ] MIDDLEWARE = [ @@ -222,6 +223,14 @@ SERVER_EMAIL = "contact@djangonaut.space" SILENCED_SYSTEM_CHECKS = ["django_recaptcha.recaptcha_test_key_error"] +EMAIL_BACKEND = "post_office.EmailBackend" + +POST_OFFICE = { + "BACKENDS": { + "default": "anymail.backends.mailjet.EmailBackend", + } +} + PUPUT_AS_PLUGIN = True PUPUT_ENTRY_MODEL = "home.models.BlogAbstract" diff --git a/indymeet/settings/dev.py b/indymeet/settings/dev.py index 778b9e53..ca9cc52b 100644 --- a/indymeet/settings/dev.py +++ b/indymeet/settings/dev.py @@ -21,7 +21,7 @@ # SECURITY WARNING: define the correct hosts in production! ALLOWED_HOSTS = ["*"] -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +POST_OFFICE["BACKENDS"] = {"default": "django.core.mail.backends.console.EmailBackend"} INSTALLED_APPS += ["django_extensions"] # noqa F405 diff --git a/indymeet/settings/production.py b/indymeet/settings/production.py index a2bd5da9..fefbac4d 100644 --- a/indymeet/settings/production.py +++ b/indymeet/settings/production.py @@ -36,7 +36,6 @@ # unless there was a setting that needs it. DATABASES["default"].setdefault("OPTIONS", {})["sslmode"] = "require" - EMAIL_BACKEND = "anymail.backends.mailjet.EmailBackend" MAILJET_API_KEY = os.getenv("MAILJET_API_KEY") MAILJET_SECRET_KEY = os.getenv("MAILJET_SECRET_KEY") ANYMAIL = { diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 68f021df..b30f63b5 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -6,81 +6,82 @@ # anyascii==0.3.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail anyio==4.2.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-core asgiref==3.7.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # django # django-browser-reload azure-core==1.29.6 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-storage-blob azure-storage-blob==12.19.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt beautifulsoup4==4.11.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail bleach==4.1.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt + # django-post-office # wagtail-markdown build==1.0.3 # via pip-tools certifi==2023.11.17 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # requests # sentry-sdk cffi==1.16.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # cryptography cfgv==3.4.0 # via pre-commit charset-normalizer==3.3.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # requests click==8.1.7 # via pip-tools cryptography==41.0.7 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-storage-blob defusedxml==0.7.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # willow distlib==0.3.8 # via virtualenv dj-database-url==2.1.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django==4.1.13 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # dj-database-url # django-anymail # django-browser-reload @@ -91,6 +92,7 @@ django==4.1.13 # django-filter # django-modelcluster # django-permissionedforms + # django-post-office # django-recaptcha # django-storages # django-taggit @@ -101,192 +103,196 @@ django==4.1.13 # wagtail django-anymail==10.3 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-browser-reload==1.12.1 # via django-tailwind django-colorful==1.3 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput django-debug-toolbar==4.3.0 # via - # -r requirements-dev.in - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-dev.in + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-el-pagination==4.0.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput django-extensions==3.2.3 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-filter==22.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail django-modelcluster==6.2.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail django-permissionedforms==0.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail +django-post-office==3.8.0 + # via -r requirements/requirements.txt django-recaptcha==4.0.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-social-share==2.3.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput django-storages==1.13.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-taggit==3.1.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput # wagtail django-tailwind==3.8.0 # via - # -r requirements-dev.in - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-dev.in + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt django-treebeard==4.7 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail django-widget-tweaks==1.5.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt djangorestframework==3.14.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail draftjs-exporter==2.1.7 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail et-xmlfile==1.1.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # openpyxl +exceptiongroup==1.2.0 + # via -r requirements/requirements-test.txt factory-boy==3.3.0 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt faker==23.2.1 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # factory-boy filelock==3.13.1 # via virtualenv filetype==1.2.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # willow freezegun==1.4.0 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt greenlet==3.0.3 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # playwright html5lib==1.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail identify==2.5.33 # via pre-commit idna==3.6 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # anyio # requests iniconfig==2.0.0 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest isodate==0.6.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-storage-blob l18n==2021.3 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail markdown==3.5.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail-markdown nodeenv==1.8.0 # via pre-commit openpyxl==3.1.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail packaging==23.2 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # bleach # build # pytest pillow==10.2.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail pip-tools==7.4.1 - # via -r requirements-dev.in + # via -r requirements/requirements-dev.in platformdirs==4.1.0 # via virtualenv playwright==1.42.0 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest-playwright pluggy==1.3.0 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest pre-commit==3.7.0 - # via -r requirements-dev.in + # via -r requirements/requirements-dev.in psycopg2-binary==2.9.9 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt puput==2.0.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt pycparser==2.21 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # cffi pyee==11.0.1 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # playwright pyproject-hooks==1.0.0 # via @@ -294,59 +300,60 @@ pyproject-hooks==1.0.0 # pip-tools pytest==7.4.4 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest-base-url # pytest-django # pytest-mock # pytest-playwright pytest-base-url==2.0.0 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest-playwright pytest-django==4.8.0 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt pytest-mock==3.14.0 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt pytest-playwright==0.4.4 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt python-dateutil==2.8.2 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # faker # freezegun python-dotenv==1.0.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt python-slugify==8.0.1 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # pytest-playwright pytz==2023.3.post1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # django-modelcluster + # django-post-office # djangorestframework # l18n pyyaml==6.0.1 # via pre-commit requests==2.31.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-core # django-anymail # pytest-base-url # wagtail sentry-sdk==1.40.4 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt six==1.16.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-core # bleach # html5lib @@ -355,47 +362,47 @@ six==1.16.0 # python-dateutil sniffio==1.3.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # anyio soupsieve==2.5 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # beautifulsoup4 sqlparse==0.4.4 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # django # django-debug-toolbar supervisor==4.2.5 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt telepath==0.3.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail text-unidecode==1.3 # via - # -r requirements-test.txt + # -r requirements/requirements-test.txt # python-slugify typing-extensions==4.10.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # azure-core # azure-storage-blob # dj-database-url # pyee unittest-parametrize==1.4.0 - # via -r requirements-test.txt + # via -r requirements/requirements-test.txt urllib3==2.2.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # django-anymail # requests # sentry-sdk @@ -403,31 +410,31 @@ virtualenv==20.25.0 # via pre-commit wagtail==5.0.5 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput # wagtail-markdown wagtail-markdown==0.11.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # puput webencodings==0.5.1 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # bleach # html5lib wheel==0.42.0 # via pip-tools whitenoise==6.6.0 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt willow==1.6.3 # via - # -r requirements-test.txt - # -r requirements.txt + # -r requirements/requirements-test.txt + # -r requirements/requirements.txt # wagtail # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/requirements-test.txt b/requirements/requirements-test.txt index 7c859948..1dbe6cec 100644 --- a/requirements/requirements-test.txt +++ b/requirements/requirements-test.txt @@ -6,56 +6,57 @@ # anyascii==0.3.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail anyio==4.2.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-core asgiref==3.7.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # django azure-core==1.29.6 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-storage-blob azure-storage-blob==12.19.0 - # via -r requirements.txt + # via -r requirements/requirements.txt beautifulsoup4==4.11.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail bleach==4.1.0 # via - # -r requirements.txt + # -r requirements/requirements.txt + # django-post-office # wagtail-markdown certifi==2023.11.17 # via - # -r requirements.txt + # -r requirements/requirements.txt # requests # sentry-sdk cffi==1.16.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # cryptography charset-normalizer==3.3.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # requests cryptography==41.0.7 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-storage-blob defusedxml==0.7.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # willow dj-database-url==2.1.0 - # via -r requirements.txt + # via -r requirements/requirements.txt django==4.1.13 # via - # -r requirements.txt + # -r requirements/requirements.txt # dj-database-url # django-anymail # django-colorful @@ -65,6 +66,7 @@ django==4.1.13 # django-filter # django-modelcluster # django-permissionedforms + # django-post-office # django-recaptcha # django-storages # django-taggit @@ -74,131 +76,133 @@ django==4.1.13 # puput # wagtail django-anymail==10.3 - # via -r requirements.txt + # via -r requirements/requirements.txt django-colorful==1.3 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput django-debug-toolbar==4.3.0 - # via -r requirements.txt + # via -r requirements/requirements.txt django-el-pagination==4.0.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput django-extensions==3.2.3 - # via -r requirements.txt + # via -r requirements/requirements.txt django-filter==22.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail django-modelcluster==6.2.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail django-permissionedforms==0.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail +django-post-office==3.8.0 + # via -r requirements/requirements.txt django-recaptcha==4.0.0 - # via -r requirements.txt + # via -r requirements/requirements.txt django-social-share==2.3.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput django-storages==1.13.2 - # via -r requirements.txt + # via -r requirements/requirements.txt django-taggit==3.1.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput # wagtail django-tailwind==3.8.0 - # via -r requirements.txt + # via -r requirements/requirements.txt django-treebeard==4.7 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail django-widget-tweaks==1.5.0 - # via -r requirements.txt + # via -r requirements/requirements.txt djangorestframework==3.14.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail draftjs-exporter==2.1.7 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail et-xmlfile==1.1.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # openpyxl factory-boy==3.3.0 - # via -r requirements-test.in + # via -r requirements/requirements-test.in faker==23.2.1 # via factory-boy filetype==1.2.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # willow freezegun==1.4.0 - # via -r requirements-test.in + # via -r requirements/requirements-test.in greenlet==3.0.3 # via playwright html5lib==1.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail idna==3.6 # via - # -r requirements.txt + # -r requirements/requirements.txt # anyio # requests iniconfig==2.0.0 # via pytest isodate==0.6.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-storage-blob l18n==2021.3 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail markdown==3.5.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail-markdown openpyxl==3.1.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail packaging==23.2 # via - # -r requirements.txt + # -r requirements/requirements.txt # bleach # pytest pillow==10.2.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail playwright==1.42.0 # via - # -r requirements-test.in + # -r requirements/requirements-test.in # pytest-playwright pluggy==1.3.0 # via pytest psycopg2-binary==2.9.9 - # via -r requirements.txt + # via -r requirements/requirements.txt puput==2.0.0 - # via -r requirements.txt + # via -r requirements/requirements.txt pycparser==2.21 # via - # -r requirements.txt + # -r requirements/requirements.txt # cffi pyee==11.0.1 # via playwright pytest==7.4.4 # via - # -r requirements-test.in + # -r requirements/requirements-test.in # pytest-base-url # pytest-django # pytest-mock @@ -206,37 +210,38 @@ pytest==7.4.4 pytest-base-url==2.0.0 # via pytest-playwright pytest-django==4.8.0 - # via -r requirements-test.in + # via -r requirements/requirements-test.in pytest-mock==3.14.0 - # via -r requirements-test.in + # via -r requirements/requirements-test.in pytest-playwright==0.4.4 - # via -r requirements-test.in + # via -r requirements/requirements-test.in python-dateutil==2.8.2 # via # faker # freezegun python-dotenv==1.0.1 - # via -r requirements.txt + # via -r requirements/requirements.txt python-slugify==8.0.1 # via pytest-playwright pytz==2023.3.post1 # via - # -r requirements.txt + # -r requirements/requirements.txt # django-modelcluster + # django-post-office # djangorestframework # l18n requests==2.31.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-core # django-anymail # pytest-base-url # wagtail sentry-sdk==1.40.4 - # via -r requirements.txt + # via -r requirements/requirements.txt six==1.16.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-core # bleach # html5lib @@ -245,59 +250,59 @@ six==1.16.0 # python-dateutil sniffio==1.3.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # anyio soupsieve==2.5 # via - # -r requirements.txt + # -r requirements/requirements.txt # beautifulsoup4 sqlparse==0.4.4 # via - # -r requirements.txt + # -r requirements/requirements.txt # django # django-debug-toolbar supervisor==4.2.5 - # via -r requirements.txt + # via -r requirements/requirements.txt telepath==0.3.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail text-unidecode==1.3 # via python-slugify typing-extensions==4.10.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # azure-core # azure-storage-blob # dj-database-url # pyee unittest-parametrize==1.4.0 - # via -r requirements-test.in + # via -r requirements/requirements-test.in urllib3==2.2.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # django-anymail # requests # sentry-sdk wagtail==5.0.5 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput # wagtail-markdown wagtail-markdown==0.11.0 # via - # -r requirements.txt + # -r requirements/requirements.txt # puput webencodings==0.5.1 # via - # -r requirements.txt + # -r requirements/requirements.txt # bleach # html5lib whitenoise==6.6.0 - # via -r requirements.txt + # via -r requirements/requirements.txt willow==1.6.3 # via - # -r requirements.txt + # -r requirements/requirements.txt # wagtail # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/requirements.in b/requirements/requirements.in index 81280adb..88df1e43 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -18,3 +18,4 @@ sentry-sdk[django] supervisor django-widget-tweaks dj-database-url +django-post_office diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 663b83e6..a1252525 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -17,7 +17,9 @@ azure-storage-blob==12.19.0 beautifulsoup4==4.11.2 # via wagtail bleach==4.1.0 - # via wagtail-markdown + # via + # django-post-office + # wagtail-markdown certifi==2023.11.17 # via # requests @@ -31,10 +33,10 @@ cryptography==41.0.7 defusedxml==0.7.1 # via willow dj-database-url==2.1.0 - # via -r requirements.in + # via -r requirements/requirements.in django==4.1.13 # via - # -r requirements.in + # -r requirements/requirements.in # dj-database-url # django-anymail # django-colorful @@ -44,6 +46,7 @@ django==4.1.13 # django-filter # django-modelcluster # django-permissionedforms + # django-post-office # django-recaptcha # django-storages # django-taggit @@ -54,37 +57,39 @@ django==4.1.13 # sentry-sdk # wagtail django-anymail==10.3 - # via -r requirements.in + # via -r requirements/requirements.in django-colorful==1.3 # via puput django-debug-toolbar==4.3.0 - # via -r requirements.in + # via -r requirements/requirements.in django-el-pagination==4.0.0 # via puput django-extensions==3.2.3 - # via -r requirements.in + # via -r requirements/requirements.in django-filter==22.1 # via wagtail django-modelcluster==6.2.1 # via wagtail django-permissionedforms==0.1 # via wagtail +django-post-office==3.8.0 + # via -r requirements/requirements.in django-recaptcha==4.0.0 - # via -r requirements.in + # via -r requirements/requirements.in django-social-share==2.3.0 # via puput django-storages==1.13.2 - # via -r requirements.in + # via -r requirements/requirements.in django-taggit==3.1.0 # via # puput # wagtail django-tailwind==3.8.0 - # via -r requirements.in + # via -r requirements/requirements.in django-treebeard==4.7 # via wagtail django-widget-tweaks==1.5.0 - # via -r requirements.in + # via -r requirements/requirements.in djangorestframework==3.14.0 # via wagtail draftjs-exporter==2.1.7 @@ -112,16 +117,17 @@ packaging==23.2 pillow==10.2.0 # via wagtail psycopg2-binary==2.9.9 - # via -r requirements.in + # via -r requirements/requirements.in puput==2.0.0 - # via -r requirements.in + # via -r requirements/requirements.in pycparser==2.21 # via cffi python-dotenv==1.0.1 - # via -r requirements.in + # via -r requirements/requirements.in pytz==2023.3.post1 # via # django-modelcluster + # django-post-office # djangorestframework # l18n requests==2.31.0 @@ -130,10 +136,10 @@ requests==2.31.0 # django-anymail # wagtail sentry-sdk==1.40.4 - # via -r requirements.in + # via -r requirements/requirements.in six==1.16.0 # via - # -r requirements.in + # -r requirements/requirements.in # azure-core # bleach # html5lib @@ -148,24 +154,24 @@ sqlparse==0.4.4 # django # django-debug-toolbar supervisor==4.2.5 - # via -r requirements.in + # via -r requirements/requirements.in telepath==0.3.1 # via wagtail typing-extensions==4.10.0 # via - # -r requirements.in + # -r requirements/requirements.in # azure-core # azure-storage-blob # dj-database-url urllib3==2.2.1 # via - # -r requirements.in + # -r requirements/requirements.in # django-anymail # requests # sentry-sdk wagtail==5.0.5 # via - # -r requirements.in + # -r requirements/requirements.in # puput # wagtail-markdown wagtail-markdown==0.11.0 @@ -175,7 +181,7 @@ webencodings==0.5.1 # bleach # html5lib whitenoise==6.6.0 - # via -r requirements.in + # via -r requirements/requirements.in willow==1.6.3 # via wagtail diff --git a/supervisord.conf b/supervisord.conf index 02d956dc..86941c09 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -3,3 +3,11 @@ nodaemon=false [program:foo] command=/bin/cat + +[program:post_office_send_queued_mail] +command=bash -c 'python /app/manage.py send_queued_mail && sleep 30' +autorestart=true + +[program:post_office_cleanup_mail] +command=bash -c 'python /app/manage.py python manage.py cleanup_mail --days=30 --delete-attachments && sleep 86400' +autorestart=true From fc2dccdf24321485096d6e43585ec8895ab8a845 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:34:47 +0200 Subject: [PATCH 02/13] Add a user survey response view. (#370) --- home/forms.py | 21 +++++++- home/managers.py | 6 +-- .../home/includes/session_apply_btn.html | 2 + home/templates/home/surveys/form.html | 6 ++- home/tests/test_session_views.py | 7 ++- .../test_user_survey_response_form_views.py | 50 +++++++++++++++++++ home/urls.py | 6 +++ home/views.py | 39 ++++++++++++++- indymeet/templates/forms/form.html | 4 +- 9 files changed, 130 insertions(+), 11 deletions(-) diff --git a/home/forms.py b/home/forms.py index c75a1993..1492fa1a 100644 --- a/home/forms.py +++ b/home/forms.py @@ -31,7 +31,7 @@ def make_choices(question: Question) -> list[tuple[str, str]]: class BaseSurveyForm(forms.Form): - def __init__(self, survey, user, *args, **kwargs): + def __init__(self, *args, survey, user, **kwargs): self.survey = survey self.user = user if user.is_authenticated else None self.field_names = [] @@ -158,3 +158,22 @@ def save(self): value=value, user_survey_response=user_survey_response, ) + + +class UserSurveyResponseForm(BaseSurveyForm): + def __init__(self, *args, instance, **kwargs): + self.survey = instance.survey + self.user_survey_response = instance + super().__init__(*args, survey=self.survey, user=instance.user, *args, **kwargs) + self._set_initial_data() + + def _set_initial_data(self): + question_responses = self.user_survey_response.userquestionresponse_set.all() + + for question_response in question_responses: + field_name = f"field_survey_{question_response.question.id}" + if question_response.question.type_field == TypeField.MULTI_SELECT: + self.fields[field_name].initial = question_response.value.split(",") + else: + self.fields[field_name].initial = question_response.value + self.fields[field_name].disabled = True diff --git a/home/managers.py b/home/managers.py index 7f03c569..e75eb29d 100644 --- a/home/managers.py +++ b/home/managers.py @@ -1,7 +1,7 @@ from __future__ import annotations -from django.db.models import Exists from django.db.models import OuterRef +from django.db.models import Subquery from django.db.models import Value from django.db.models.query import QuerySet from django.utils import timezone @@ -37,10 +37,10 @@ def with_applications(self, user): if user.is_anonymous: return self.annotate(completed_application=Value(False)) return self.annotate( - completed_application=Exists( + completed_application=Subquery( UserSurveyResponse.objects.filter( survey_id=OuterRef("application_survey_id"), user_id=user.id - ) + ).values("id")[:1] ) ) diff --git a/home/templates/home/includes/session_apply_btn.html b/home/templates/home/includes/session_apply_btn.html index 0a6e9fca..ff3abfcd 100644 --- a/home/templates/home/includes/session_apply_btn.html +++ b/home/templates/home/includes/session_apply_btn.html @@ -3,5 +3,7 @@ Apply {% elif request.user.is_authenticated and not session.completed_application and request.user.profile.email_confirmed %} Apply + {% elif session.completed_application %} + View Application {% endif %} {% endif %} diff --git a/home/templates/home/surveys/form.html b/home/templates/home/surveys/form.html index f964ea40..213e6f79 100644 --- a/home/templates/home/surveys/form.html +++ b/home/templates/home/surveys/form.html @@ -21,9 +21,9 @@

{{ title_page }}

{{ field.help_text|linebreaksbr|urlize }}

{% if field.field.widget.input_type == 'radio' or field.field.widget.input_type == 'checkbox' %} - {{ field }} + {{ field|addclass:'read-only:text-slate-700' }} {% else %} - {{ field|addclass:'w-full p-4 pr-12 text-sm border-gray-500 rounded-lg shadow-sm' }} + {{ field|addclass:'w-full p-4 pr-12 text-sm border-gray-500 rounded-lg shadow-sm disabled:bg-slate-50 disabled:text-slate-700 disabled:border-slate-200' }} {% endif %}
{{ field.errors }} @@ -31,9 +31,11 @@

{{ title_page }}

{% endfor %} + {% if not read_only %} + {% endif %} diff --git a/home/tests/test_session_views.py b/home/tests/test_session_views.py index ad20b561..2c16fd5d 100644 --- a/home/tests/test_session_views.py +++ b/home/tests/test_session_views.py @@ -84,7 +84,7 @@ def test_session_list_email_confirmed(self): def test_session_list_email_confirmed_already_applied(self): user = UserFactory.create(profile__email_confirmed=True) - UserSurveyResponseFactory(survey=self.survey, user=user) + survey_response = UserSurveyResponseFactory(survey=self.survey, user=user) self.client.force_login(user) response = self.client.get(reverse("session_list")) self.assertEqual(response.status_code, 200) @@ -96,6 +96,11 @@ def test_session_list_email_confirmed_already_applied(self): ) self.assertNotContains(response, "Your email is not confirmed!") self.assertNotContains(response, "You may not be able to apply for sessions") + self.assertContains(response, "View Application") + survey_detail_url = reverse( + "user_survey_response", kwargs={"pk": survey_response.id} + ) + self.assertContains(response, survey_detail_url) def test_session_detail_open_application(self): url = reverse( diff --git a/home/tests/test_user_survey_response_form_views.py b/home/tests/test_user_survey_response_form_views.py index 4cbc0588..0ffa5f85 100644 --- a/home/tests/test_user_survey_response_form_views.py +++ b/home/tests/test_user_survey_response_form_views.py @@ -6,6 +6,7 @@ from accounts.factories import UserFactory from home.factories import QuestionFactory from home.factories import SurveyFactory +from home.factories import UserQuestionResponseFactory from home.factories import UserSurveyResponseFactory from home.models import UserQuestionResponse from home.models import UserSurveyResponse @@ -72,3 +73,52 @@ def test_success_message(self): ).value, "Amazing", ) + + +class UserSurveyResponseViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.survey = SurveyFactory.create( + name="Test Survey", description="This is a description of the survey!" + ) + cls.user = UserFactory.create() + cls.question_1 = QuestionFactory.create( + survey=cls.survey, + label="How are you?", + ) + cls.question_2 = QuestionFactory.create( + survey=cls.survey, + label="What is your favourite food?", + ) + cls.survey_response = UserSurveyResponseFactory( + survey=cls.survey, user=cls.user + ) + UserQuestionResponseFactory( + user_survey_response=cls.survey_response, + question=cls.question_1, + value="Very good", + ) + UserQuestionResponseFactory( + user_survey_response=cls.survey_response, + question=cls.question_2, + value="Pizza", + ) + cls.url = reverse("user_survey_response", kwargs={"pk": cls.survey_response.id}) + + def test_success_get(self): + self.client.force_login(self.user) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Test Survey") + self.assertContains(response, "This is a description of the survey!") + self.assertContains(response, "How are you?") + self.assertContains(response, "Very good") + self.assertContains(response, "What is your favourite food?") + self.assertContains(response, "Pizza") + self.assertNotContains(response, "Submit") + + def test_cannot_view_others_survey_response(self): + different_user = UserFactory.create() + self.client.force_login(different_user) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 403) diff --git a/home/urls.py b/home/urls.py index 89c6431b..60ef0722 100644 --- a/home/urls.py +++ b/home/urls.py @@ -8,6 +8,7 @@ from .views import EventListView from .views import SessionDetailView from .views import SessionListView +from .views import UserSurveyResponseView urlpatterns = [ path("calendar/", event_calendar, name="calendar"), @@ -24,4 +25,9 @@ CreateUserSurveyResponseFormView.as_view(), name="survey_response_create", ), + path( + "survey_response//", + UserSurveyResponseView.as_view(), + name="user_survey_response", + ), ] diff --git a/home/views.py b/home/views.py index bec9f310..c40d431f 100644 --- a/home/views.py +++ b/home/views.py @@ -5,16 +5,20 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import UserPassesTestMixin +from django.db.models import Prefetch from django.shortcuts import render from django.urls import reverse_lazy from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin +from django.views.generic.edit import ModelFormMixin from django.views.generic.list import ListView from .forms import CreateUserSurveyResponseForm +from .forms import UserSurveyResponseForm from .models import Event from .models import Session from .models import Survey +from .models import UserQuestionResponse from .models import UserSurveyResponse @@ -114,11 +118,12 @@ class CreateUserSurveyResponseFormView( template_name = "home/surveys/form.html" def test_func(self): - survey = self.get_object() user = self.request.user return ( user.profile.email_confirmed - and not UserSurveyResponse.objects.filter(survey=survey, user=user).exists() + and not UserSurveyResponse.objects.filter( + survey__slug=self.kwargs.get(self.slug_url_kwarg), user=user + ).exists() ) def get_form_kwargs(self): @@ -143,3 +148,33 @@ def post(self, request, *args, **kwargs): else: messages.error(self.request, gettext("Something went wrong.")) return self.form_invalid(form) + + +class UserSurveyResponseView( + LoginRequiredMixin, UserPassesTestMixin, ModelFormMixin, DetailView +): + model = UserSurveyResponse + form_class = UserSurveyResponseForm + success_url = reverse_lazy("session_list") + template_name = "home/surveys/form.html" + + def get_queryset(self): + return UserSurveyResponse.objects.select_related("survey").prefetch_related( + Prefetch( + "userquestionresponse_set", + queryset=UserQuestionResponse.objects.select_related("question"), + ) + ) + + def test_func(self): + return UserSurveyResponse.objects.filter( + user=self.request.user, id=self.kwargs.get(self.pk_url_kwarg) + ).exists() + + def get_context_data(self, **kwargs): + survey = self.object.survey + kwargs["title_page"] = survey.name + kwargs["sub_title_page"] = survey.description + kwargs["read_only"] = True + context_data = super().get_context_data(**kwargs) + return context_data diff --git a/indymeet/templates/forms/form.html b/indymeet/templates/forms/form.html index 0b488b36..eb544fc6 100644 --- a/indymeet/templates/forms/form.html +++ b/indymeet/templates/forms/form.html @@ -9,9 +9,9 @@