diff --git a/Makefile b/Makefile index cb0c6182c..d3fb22aae 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ help: ## ⁉️ - Display help comments for each make command | sort setup: build ## 🔨 - Set instance up - docker-compose run web django-admin migrate - docker-compose run web django-admin createcachetable + docker-compose run --rm web django-admin migrate + docker-compose run --rm web django-admin createcachetable build: ## 🔨 - Build Docker container bash -c "docker-compose build --build-arg UID=$$(id -u) --build-arg GID=$$(id -g)" @@ -28,10 +28,10 @@ runserver: ## 🏃 - Run Django server docker-compose exec web django-admin runserver 0.0.0.0:8000 superuser: ## 🔒 - Create superuser - docker-compose run web django-admin createsuperuser + docker-compose run --rm web django-admin createsuperuser migrations: ## 🧳 - Make migrations - docker-compose run web django-admin makemigrations + docker-compose run --rm web django-admin makemigrations migrate: ## 🧳 - Migrate - docker-compose run web django-admin migrate + docker-compose run --rm web django-admin migrate diff --git a/fabfile.py b/fabfile.py index 0a2a98d20..eacaa62aa 100644 --- a/fabfile.py +++ b/fabfile.py @@ -114,7 +114,7 @@ def import_data( def delete_local_renditions(c, local_database_name=LOCAL_DATABASE_NAME): - psql(c, "DELETE FROM images_rendition;") + psql(c, "DELETE FROM wagtailimages_rendition;") ######### diff --git a/poetry.lock b/poetry.lock index d3bfbc29f..ada1ce76e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "anyascii" @@ -645,6 +645,23 @@ Django = ">=3.2" dev = ["black (==24.1.1)", "blacken-docs (==1.16.0)", "coverage (==7.3.4)", "django-stubs[compatible-mypy] (==4.2.7)", "flake8 (==7.0.0)", "flake8-bugbear", "flake8-comprehensions", "isort (==5.13.2)", "mypy (==1.7.1)", "pre-commit (==3.4.0)", "tox (==4.12.1)", "tox-gh-actions (==3.2.0)", "types-requests (==2.31.0.20240125)", "virtualenv-pyenv (==0.4.0)"] testing = ["coverage (==7.3.4)", "dj-database-url (==2.1.0)"] +[[package]] +name = "mailchimp-marketing" +version = "3.0.80" +description = "Mailchimp Marketing API" +optional = false +python-versions = "*" +files = [ + {file = "mailchimp_marketing-3.0.80-py3-none-any.whl", hash = "sha256:a5bcb3ebd3be60908c65af765f8195724e6fd2d61ecf6da667a794c5bc7b84d3"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +python-dateutil = ">=2.1" +requests = ">=2.23" +six = ">=1.10" +urllib3 = ">=1.23" + [[package]] name = "markdown" version = "3.7" @@ -660,6 +677,90 @@ files = [ docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] +[[package]] +name = "mrml" +version = "0.2.0" +description = "A Python wrapper for MRML (Rust port of MJML)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f750a2fe54b51b01b7218f3a94ffb5f94596522d750716263a885d50ff6d513"}, + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9a1e3519b8558e80eb17047524b213c4c3533a2ed98c8d5c6a218abfc6e08c1"}, + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaa9a3840354161b626c59912b96902e90dbd3903ee2dd286c4f00292eff2afd"}, + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72608e541d72c4be5176f94a6a598bb03f4f4deaefa911084c075ee3817de2e4"}, + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad775127a66149947795cb1a6139f8de7f66eafc20db69053824ca0731a04c27"}, + {file = "mrml-0.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:e60cc563a033152063a64c3e24aedc86c209b4c22ebfd2658716895f179de715"}, + {file = "mrml-0.2.0-cp310-cp310-win32.whl", hash = "sha256:2c749075da547475894cbca25b975c5c9dbe4617ecc5fef16934a274be16eb51"}, + {file = "mrml-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:031ea109fd5518680f0c464202e6d5b7ceb2d4d3a78351941fc8869af6772394"}, + {file = "mrml-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:edef9deb51e0ee64b18fd6016281d03cddbf6a25e705da6a79c0818c24b684d6"}, + {file = "mrml-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d00a8599b7be3ee1b405fb780a141ac81fa383e404e1618d551d2a59ca76289"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea7913ef29a779349f6270cbf6473579c136aa3456c01650ead6365121717f5c"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe793a5000cce0f2c9267239ac3e8f51479c86ea2f6d0afc4f55f168fdc9a167"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cd384069d98fd8515a84cc37f2ab2e1cf85fb88a6db568c76682038909c45b3"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bfa7bec9fd9e531fa560f4b36520a51f129fe94f56f4b8bcbf2a146d23448ce"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af3e6aea8e3d26489be397ba466d0e7d75747a5f904300ade14a5592e172a9f6"}, + {file = "mrml-0.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:66771a03eb17ad6d2264489802c89e849b0f3af791c0ed71d9679b8462322aaf"}, + {file = "mrml-0.2.0-cp311-cp311-win32.whl", hash = "sha256:b821a410c4b74ad3892e1f280823144d6511cd12a22d7ab16ed72b89773382e5"}, + {file = "mrml-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:66ecc03a0d964f8f307b67e3f2a5cbfe17401f3454ace6e9567b079150efcf54"}, + {file = "mrml-0.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b24f3e3a0924387c5927aa3b4750b0b006dedf8df97a7911a6a1a971ae2c94d2"}, + {file = "mrml-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09a1cb67b8a9973226459d40d5ed61474aedc2d81222cb502bccb842a762e60f"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:412db41d313f405f745468ea3474abef2f824e18e55dec1ee73453c924a8e1dc"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e108eef1277c699541cb102516b23b6078aae5bdb74b06cffaa0aebeda388"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eca55a6b10a877559c2ca5ce71f83989a14696a000215b4fcba3fc81c55b8912"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb375c91d15fe223cffa38831e7be01843977fb2cb91ea9d66ffd671077f08f1"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5e6bfac107a9e7e4142a18fccfb5475e13504a0b317a90659c685952120f204"}, + {file = "mrml-0.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c14756543e2fb59c04b804211d5438eac79e19290ee56552ded936b29119c8ec"}, + {file = "mrml-0.2.0-cp312-cp312-win32.whl", hash = "sha256:a4707afeb41804a1a7a5c3891b795b2debc4ee3c3b3133c4841ccc8a5f65f25a"}, + {file = "mrml-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:4cc5c733fa37b5adb9d7793a1b4d415e8222ae9c8f40454c32febae791c7bd65"}, + {file = "mrml-0.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d1a5a55aed7425c59e5b77024ea3876c71a6ea62792cf4ce38eb04100fbb0a61"}, + {file = "mrml-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d6f09ca2076b4d1080fdf0cd1a3a2ad85faa88f64690cef13d696c3bc30d5a5b"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:986b71ab1c0ff78aef6d7aaac967418083146a6003de5ca5f7275f5ded190b09"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:219a840471e87e4d3d5db5b9c9ff65a96410643f02994fa46b15a308c8001f54"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eba7ae5cbc4263fa3ba94230d6c66008c33890b5372385ed6ab8951bac336771"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df66107b0dd7556b21cd3156876f2d5389d276b70cf29b8796141b0553a9615e"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35233367f3ce503f87f47c04838ae8a298b5b08a212b1753e998279b09f1ab46"}, + {file = "mrml-0.2.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0b0915d8ec0964333c123d462bfd68284a3d4715971d978426e282e11d08ebb4"}, + {file = "mrml-0.2.0-cp313-cp313-win32.whl", hash = "sha256:302cbbc0846fc158d2ec6b3e7c59dad6f5a9adcd3bd9d26b2dd148d39a274711"}, + {file = "mrml-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:29d95f366dd2a55d152965ec90035b5c4a89d2f9028bd4bb2342e2f3585363e9"}, + {file = "mrml-0.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13159e3aa30696ac64af5176a1a75020a7097fe2338e126d2ddc734f73549a10"}, + {file = "mrml-0.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1becb9f3e278027c3d99747e30f8e8915b9569b6756028f2e824fdf35745e37"}, + {file = "mrml-0.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7e3755fbc661115e3d7f1614230ac1c0bb9e8fdb4213d36aa1ad057f8fcf18c"}, + {file = "mrml-0.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:55a82f3ed95d6b402e39446762f7bbeeb2a68084b82a10abe2923f0abe73259b"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6d2c9995e8ca89ca5efd5db646556350e8149de3b84cfa39ee44c27afe5e913"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b29eefe8a800cb7550927c4cd1888c5e98aa9f6fe6f2ed19efa80e47ba1f31a0"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e3fd2e2ea79589c23d0f2bed268241db7a6747983d7a443e36d7b190394d31b"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fdb4f8eaa4fc80c606e1dbe03730facf60463a1476e9ccf47b8e28b04e01d29"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09bd9472b5349a870e315910ac67a8329fa6b6e24c7ae71e770d09221707c55a"}, + {file = "mrml-0.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:2c4382214f735f3c976e694d2ad33ad62ef190f52d407088b8836acfe90c2223"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6202c860a9aa73f2d3a964a74836dd43d14f248a5a2c07b73cf61b099c802257"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:337a5089e1c797dd62c9ccfb3a67cdb516596829db0905ea9fac953fef197f20"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba5ce8f6bf84f20a2ed496b8e3aee5b088a9e1a629ae3aa22bdc2a8dde0c1848"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019abb39c821cc995b315eabec8847fadad860c881bfbec94497da54c8dd1b89"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dea2a4da41e7c20ca1879dc4e70f990d9e757477fae9a83332ddbc5acbe05164"}, + {file = "mrml-0.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fc984f65613b4521449c78d1527bd81c3e8d7fbc7c22aeab13928936d3a140d"}, + {file = "mrml-0.2.0-cp38-cp38-win32.whl", hash = "sha256:10f9c989296420ca5dddfe944e7552286f0db93150802a2ca94b4a3e49c6765c"}, + {file = "mrml-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1556767f1fa480443c9cf246ee3dd2f023e4a044e3eafe0b9ac695abd1f1cc5"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ef0fa91540f3b1f9a806659be7af0075426a84532691692e0df6f1432909d95"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2afd6b5267ca8f6b8dbe9f2607c37c21817620aa7a1345dd4dce4d9dc227711b"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20b3f879323bdad9dac1374d26f09c13700863c82e995b722627bad6ec458f"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56055e1940506c08f5982bb711b48b1f7b70d01fef68c6db9f1a280ba8195444"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3745cd07fa437eee1714d2791c42ed90a31d52f32490f8c1cef2af278c1229cd"}, + {file = "mrml-0.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ef2cddc25bbd7a96201ee5eacc58082e5f13fd9bea96d3acb278a6cf7258b770"}, + {file = "mrml-0.2.0-cp39-cp39-win32.whl", hash = "sha256:8417589a0ff3c42be896f3a50933b3daced790552ea2f03551e312648a57f199"}, + {file = "mrml-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b665085282b224632f17c679fbd3f0f1e95c1746d1cbcdad1a86202829fa573"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:777695190e15e88d7f28310f23f3e684db52969e4545a8ef115ba9a896651341"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56098aa83ae53b37e7fd751791b968519ca2905475594e907601e2e1415d2de0"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89f208f1acac13a09cd742e8a51f964b6a84161b07a275e3476372de8e440038"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a883e58ad0c863c848f2f4e84bb251ce3596aba4323314a5b2e56892e87810f"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d54d73a9571cc2b9060133ecdcc968f0fb42b523dfb8cb046d29bfcdab11b9"}, + {file = "mrml-0.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:46a2737ef31016c8c78683cf923f9d2fd2ae02cbbab90806b45e017948f1a7f2"}, + {file = "mrml-0.2.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99b5a8f93ff8bb7a150ac98a2b3fda16ae19f01c8f52f615f2daae6cdbfbace4"}, + {file = "mrml-0.2.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:934cc47209c24f786d68724b89caad270500bfaf05d24ce57ee3845f555e36e9"}, + {file = "mrml-0.2.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e933f0372b640c0eb99008de192c497685c9d4f0d7fed973b7e30b7ccfba4c79"}, + {file = "mrml-0.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8f0d25550d110d9a9c4ee9bada85170332a1c6bd4854c44a6f8779b802bad393"}, + {file = "mrml-0.2.0.tar.gz", hash = "sha256:97e8eb99d6e42821a020bdc76a7415fb714a2d8b8a8c5c22803dcd5a809f2edd"}, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1060,6 +1161,23 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "queryish" +version = "0.2" +description = "A library for constructing queries on arbitrary data sources following Django's QuerySet API" +optional = false +python-versions = ">=3.7" +files = [ + {file = "queryish-0.2-py3-none-any.whl", hash = "sha256:2e460537f6b7cd5af187b78a3635e80ceec421221adb62883282d419dc170ea8"}, + {file = "queryish-0.2.tar.gz", hash = "sha256:60150be41673af3d0597f78fb5e77be0e30dc49658a83d274b2a0959c4f97c1b"}, +] + +[package.dependencies] +requests = ">=2.28,<3.0" + +[package.extras] +testing = ["responses (>=0.23,<1.0)"] + [[package]] name = "redis" version = "6.4.0" @@ -1430,6 +1548,31 @@ files = [ {file = "wagtail_font_awesome_svg-1.1.tar.gz", hash = "sha256:f2a19c23d071e58157a3ac4cea61c1dfeddf6cc337d8c99bc88aa0fe52d1ff61"}, ] +[[package]] +name = "wagtail-newsletter" +version = "0.2.3" +description = "Turn Wagtail pages into newsletters." +optional = false +python-versions = ">=3.10" +files = [ + {file = "wagtail_newsletter-0.2.3-py3-none-any.whl", hash = "sha256:84c6de483041ed372bbaf1dd088b5a9d3d5a446d7ae1efc2476d764c495272e9"}, + {file = "wagtail_newsletter-0.2.3.tar.gz", hash = "sha256:c1699dba67cb69da716518356856259d834eae6e05e3729f122a760e70c8616d"}, +] + +[package.dependencies] +Django = ">=4.2" +mailchimp-marketing = {version = ">=3.0.80", optional = true, markers = "extra == \"mailchimp\""} +mrml = {version = ">=0.2", optional = true, markers = "extra == \"mrml\""} +queryish = ">=0.2" +Wagtail = ">=6.3" + +[package.extras] +dev = ["flit", "psycopg", "wagtail-newsletter[docs,mailchimp,mrml,testing]"] +docs = ["sphinx", "sphinx-autobuild", "sphinx-wagtail-theme"] +mailchimp = ["mailchimp-marketing (>=3.0.80)"] +mrml = ["mrml (>=0.2)"] +testing = ["dj-database-url", "django-debug-toolbar", "django-stubs", "pyright", "pytest", "pytest-cov", "pytest-django"] + [[package]] name = "wagtailmedia" version = "0.16.0" @@ -1579,4 +1722,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.13" -content-hash = "f032ed0d17ee92fd7c6c4106aa7eec7e2f037bad7c686c98a873eec89325fd55" +content-hash = "0a6b1f48b1a41208bf043aa3485e60b1f4d31e7c855bf6cf3b3773f716f8f184" diff --git a/pyproject.toml b/pyproject.toml index 9f9146b01..f86041d45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ wagtail-font-awesome-svg = "~1.1" wagtailmedia = "~0.16" whitenoise = "~6.9" django-redis = "~6.0" +wagtail-newsletter = {extras = ["mailchimp", "mrml"], version = "^0.2.3"} [tool.poetry.group.dev.dependencies] pre-commit = "4.3.0" diff --git a/wagtailio/newsletter/blocks.py b/wagtailio/newsletter/blocks.py new file mode 100644 index 000000000..1ea0a6e13 --- /dev/null +++ b/wagtailio/newsletter/blocks.py @@ -0,0 +1,89 @@ +from django.core.exceptions import ValidationError + +from wagtail.blocks import ( + CharBlock, + PageChooserBlock, + StreamBlock, + StructBlock, + StructValue, + URLBlock, +) +from wagtail.blocks import RichTextBlock as WagtailRichTextBlock +from wagtail.images.blocks import ImageChooserBlock + + +NEWSLETTER_RICHTEXT_FEATURES = ["h2", "bold", "italic", "ol", "ul", "hr", "link"] + + +class HeadingBlock(CharBlock): + class Meta: + icon = "title" + form_classname = "title" + template = "newsletter/blocks/heading.mjml" + label = "Heading" + + +class RichTextBlock(WagtailRichTextBlock): + class Meta: + icon = "pilcrow" + template = "newsletter/blocks/rich_text.mjml" + label = "Rich Text" + + +class AccentRichTextBlock(WagtailRichTextBlock): + class Meta: + icon = "pilcrow" + template = "newsletter/blocks/accent_rich_text.mjml" + label = "Accent Rich Text" + + +class ImageBlock(ImageChooserBlock): + class Meta: + icon = "image" + template = "newsletter/blocks/image.mjml" + label = "Image" + + +class ButtonValue(StructValue): + @property + def link_url(self): + if self.get("url"): + return self.get("url") + elif self.get("page"): + return self.get("page").url + return None + + +class ButtonBlock(StructBlock): + text = CharBlock(required=True) + url = URLBlock(required=False, help_text="External URL to link to") + page = PageChooserBlock(required=False, help_text="Internal page to link to") + + def clean(self, value): + cleaned_data = super().clean(value) + url = cleaned_data.get("url") + page = cleaned_data.get("page") + + if not url and not page: + raise ValidationError( + "Please provide either a URL or select a page to link to." + ) + + if url and page: + raise ValidationError("Please provide either a URL or a page, not both.") + + return cleaned_data + + class Meta: + icon = "link" + template = "newsletter/blocks/button.mjml" + label = "Button" + value_class = ButtonValue + + +class NewsletterContentBlock(StreamBlock): + heading = HeadingBlock() + rich_text = RichTextBlock(features=NEWSLETTER_RICHTEXT_FEATURES) + accent_rich_text = AccentRichTextBlock(features=NEWSLETTER_RICHTEXT_FEATURES) + image = ImageBlock() + button = ButtonBlock() diff --git a/wagtailio/newsletter/management/__init__.py b/wagtailio/newsletter/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtailio/newsletter/management/commands/__init__.py b/wagtailio/newsletter/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtailio/newsletter/management/commands/import_newsletter.py b/wagtailio/newsletter/management/commands/import_newsletter.py new file mode 100644 index 000000000..0caa99e35 --- /dev/null +++ b/wagtailio/newsletter/management/commands/import_newsletter.py @@ -0,0 +1,177 @@ +from datetime import datetime +from io import BytesIO +from pprint import pprint + +from django.core.files.images import ImageFile +from django.core.management.base import BaseCommand + +from bs4 import BeautifulSoup, Tag +import requests +import willow + +from wagtailio.images.models import WagtailIOImage +from wagtailio.newsletter.models import NewsletterIndexPage, NewsletterPage + + +def clean_tag(tag): + if tag.name == "a": + href = tag.get("href", "") + tag.attrs = {"href": href} if href else {} + else: + tag.attrs = {} + for child in tag.find_all(True): + clean_tag(child) + return tag + + +def parse_newsletter_html(soup: Tag): + h1 = soup.select("h1")[0] + title_tr = h1.findParent("tr") + + while not title_tr.previous_sibling: + # On issue 160, the title is inside two nested `tr` tags. + # On issue 187, it's 12 nested `tr` tags deep, hence the loop. Don't ask. + title_tr = title_tr.findParent("tr") + + tr = title_tr.previous_sibling # the row that contains the date + while tr: + yield tr + tr = tr.next_sibling + + +def parse_date(date_str): + return datetime.strptime(date_str, "%d %B %Y").date() + + +def get_or_create_image(image_url): + description = f"newsletter - downloaded from {image_url}" + existing_image = WagtailIOImage.objects.filter(description=description).first() + if existing_image: + return existing_image + + response = requests.get(image_url, timeout=10) + response.raise_for_status() + + filename = image_url.split("/")[-1] + + img_bytes = BytesIO(response.content) + willow_image = willow.Image.open(img_bytes) + width, height = willow_image.get_size() + + image = WagtailIOImage( + title=filename, + description=description, + file=ImageFile(BytesIO(response.content), name=filename), + width=width, + height=height, + ) + image.save() + return image + + +def process_block_content(block): + h1 = block.find("h1") + if h1: + return {"type": "heading", "value": h1.get_text().strip()} + + button_table = block.select_one("table.mceButtonContainer") or block.select_one( + "td.mceButton" + ) + if button_table: + link = button_table.find("a") + if link: + return { + "type": "button", + "value": { + "text": link.get_text().strip(), + "url": link.get("href", ""), + "page": None, + }, + } + + img = block.find("img") + if img and img.get("src"): + image_url = img.get("src") + image = get_or_create_image(image_url) + return { + "type": "image", + "value": image.id, + } + + paragraphs = block.find_all("p") + if paragraphs: + cleaned_paragraphs = [clean_tag(p) for p in paragraphs] + content = "
".join(p.decode_contents() for p in cleaned_paragraphs) + return {"type": "rich_text", "value": f"

{content}

"} + + +def process_newsletter_content(url): + response = requests.get(url, timeout=10) + response.raise_for_status() + html_content = response.text + + soup = BeautifulSoup(html_content, "html.parser") + + newsletter_subject = soup.title.string.strip() + if newsletter_subject.startswith("This Week in Wagtail:"): + newsletter_subject = soup.title.string.split(":", 1)[1].strip() + + blocks_iterator = parse_newsletter_html(soup) + + date_block = next(blocks_iterator) + date_text = date_block.get_text().strip() + newsletter_date = parse_date(date_text) + + body = [] + for block in blocks_iterator: + text = block.get_text().strip() + if text.startswith("Until next time, thank you for reading"): + break + + if block_content := process_block_content(block): + body.append(block_content) + + return { + "date": newsletter_date, + "body": body, + "newsletter_subject": newsletter_subject, + } + + +class Command(BaseCommand): + help = "Import newsletter content from HTML files" + + def add_arguments(self, parser): + parser.add_argument("url", type=str, help="URL of the newsletter HTML file") + parser.add_argument("title", type=str, help="Title for the newsletter page") + parser.add_argument( + "--debug", + action="store_true", + help="Print debug information", + ) + + def handle(self, *args, **options): + url = options["url"] + title = options["title"] + index_page = NewsletterIndexPage.objects.get() + + newsletter_data = process_newsletter_content(url) + newsletter_data["title"] = title + + if options["debug"]: + pprint(newsletter_data["body"]) # noqa: T203 + + existing_page = ( + NewsletterPage.objects.child_of(index_page).filter(title=title).first() + ) + + if existing_page: + existing_page.body = newsletter_data["body"] + existing_page.newsletter_subject = newsletter_data["newsletter_subject"] + existing_page.date = newsletter_data["date"] + existing_page.save_revision().publish() + else: + newsletter_page = NewsletterPage(**newsletter_data) + + index_page.add_child(instance=newsletter_page) + newsletter_page.save_revision().publish() diff --git a/wagtailio/newsletter/management/commands/import_newsletter_archive.py b/wagtailio/newsletter/management/commands/import_newsletter_archive.py new file mode 100644 index 000000000..042028a8c --- /dev/null +++ b/wagtailio/newsletter/management/commands/import_newsletter_archive.py @@ -0,0 +1,76 @@ +from django.core.management import call_command +from django.core.management.base import BaseCommand + + +ARCHIVE_URLS = { + "137": "http://eepurl.com/ivTG6c", + "138": "http://eepurl.com/iwZ7G6", + "139": "http://eepurl.com/ixNV4w", + "140": "http://eepurl.com/iyhGYM", + "141": "http://eepurl.com/iyLQJc", + "142": "http://eepurl.com/iz9nMQ", + "143": "http://eepurl.com/iA6xHw", + "144": "http://eepurl.com/iB42Jg", + "145": "http://eepurl.com/iC0wWc", + "146": "http://eepurl.com/iDy-C6", + "147": "http://eepurl.com/iD5k_E", + "148": "http://eepurl.com/iE1QSQ", + "149": "http://eepurl.com/iF-z82", + "150": "http://eepurl.com/iHNgmU", + "151": "http://eepurl.com/iIIKyA", + "152": "http://eepurl.com/iJFzbY", + "153": "http://eepurl.com/iKHkas", + "154": "http://eepurl.com/iLyeCs", + "155": "http://eepurl.com/iMuT3E", + "156": "http://eepurl.com/iNnQKE", + "157": "http://eepurl.com/iOc4L2", + "158": "http://eepurl.com/iPcRkw", + "159": "http://eepurl.com/iP2mTE", + "160": "http://eepurl.com/iQW_y2", + "161": "http://eepurl.com/iRVoYw", + "162": "http://eepurl.com/iSHTWI", + "164": "http://eepurl.com/iTA9b-", + "165": "http://eepurl.com/iUnY6Y", + "166": "http://eepurl.com/iWD_hE", + "167": "http://eepurl.com/iXooRM", + "168": "http://eepurl.com/iYlhYU", + "169": "http://eepurl.com/iYMLro", + "170": "http://eepurl.com/i0FTI2", + "171": "http://eepurl.com/i0GS8s", + "172": "http://eepurl.com/i1BZRU", + "173": "http://eepurl.com/i2KzkE", + "174": "http://eepurl.com/i367AI", + "175": "http://eepurl.com/i4ZGe6", + "176": "http://eepurl.com/i53IVA", + "177": "http://eepurl.com/i7ouKk", + "178": "http://eepurl.com/i8devU", + "179": "http://eepurl.com/i87DBY", + "180": "http://eepurl.com/i-gM12", + "181": "http://eepurl.com/i_rL9Y", + "182": "http://eepurl.com/jarS22", + "183": "http://eepurl.com/jbyaXQ", + "184": "http://eepurl.com/jcHhc6", + "185": "http://eepurl.com/jdEVZo", + "186": "http://eepurl.com/jepIFY", + "187": "http://eepurl.com/jeIw_Y", + "188": "https://mailchi.mp/wagtail/twiw-11036059", + "189": "https://mailchi.mp/wagtail/twiw-11036356", + "190": "https://mailchi.mp/wagtail/twiw-11036662", + "191": "https://mailchi.mp/wagtail/twiw-11036961", + "192": "https://mailchi.mp/wagtail/twiw-11037103", + "193": "https://mailchi.mp/wagtail/twiw-11037192", + "194": "https://mailchi.mp/wagtail/twiw-11037409", + "195": "https://mailchi.mp/wagtail/twiw-11037646", + "196": "https://mailchi.mp/wagtail/twiw-11037909", +} + + +class Command(BaseCommand): + help = "Import all archived newsletters" + + def handle(self, *args, **options): + for issue_num, url in ARCHIVE_URLS.items(): + title = f"Issue #{issue_num}" + self.stdout.write(f"Importing {title}...") + call_command("import_newsletter", url, title) + self.stdout.write(self.style.SUCCESS(f"Successfully imported {title}")) diff --git a/wagtailio/newsletter/migrations/0004_wagtail_newsletter_model.py b/wagtailio/newsletter/migrations/0004_wagtail_newsletter_model.py new file mode 100644 index 000000000..126f002e3 --- /dev/null +++ b/wagtailio/newsletter/migrations/0004_wagtail_newsletter_model.py @@ -0,0 +1,200 @@ +# Generated by Django 5.1.6 on 2025-04-16 13:02 + +from django.db import migrations, models +import django.db.models.deletion + +import wagtail.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("newsletter", "0003_newsletteremailaddress_signed_up_at"), + ("wagtail_newsletter", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="NewsletterSettings", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "footer", + wagtail.fields.StreamField( + [ + ("heading", 0), + ("rich_text", 1), + ("accent_rich_text", 2), + ("image", 3), + ("button", 7), + ], + blank=True, + block_lookup={ + 0: ("wagtailio.newsletter.blocks.HeadingBlock", (), {}), + 1: ( + "wagtailio.newsletter.blocks.RichTextBlock", + (), + { + "features": [ + "h2", + "bold", + "italic", + "ol", + "ul", + "hr", + "link", + ] + }, + ), + 2: ( + "wagtailio.newsletter.blocks.AccentRichTextBlock", + (), + { + "features": [ + "h2", + "bold", + "italic", + "ol", + "ul", + "hr", + "link", + ] + }, + ), + 3: ("wagtailio.newsletter.blocks.ImageBlock", (), {}), + 4: ("wagtail.blocks.CharBlock", (), {"required": True}), + 5: ( + "wagtail.blocks.URLBlock", + (), + { + "help_text": "External URL to link to", + "required": False, + }, + ), + 6: ( + "wagtail.blocks.PageChooserBlock", + (), + { + "help_text": "Internal page to link to", + "required": False, + }, + ), + 7: ( + "wagtail.blocks.StructBlock", + [[("text", 4), ("url", 5), ("page", 6)]], + {}, + ), + }, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="newsletterpage", + name="preview", + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name="newsletterpage", + name="content", + field=wagtail.fields.StreamField( + [ + ("heading", 0), + ("rich_text", 1), + ("accent_rich_text", 2), + ("image", 3), + ("button", 7), + ], + blank=True, + block_lookup={ + 0: ("wagtailio.newsletter.blocks.HeadingBlock", (), {}), + 1: ( + "wagtailio.newsletter.blocks.RichTextBlock", + (), + { + "features": [ + "h2", + "bold", + "italic", + "ol", + "ul", + "hr", + "link", + ] + }, + ), + 2: ( + "wagtailio.newsletter.blocks.AccentRichTextBlock", + (), + { + "features": [ + "h2", + "bold", + "italic", + "ol", + "ul", + "hr", + "link", + ] + }, + ), + 3: ("wagtailio.newsletter.blocks.ImageBlock", (), {}), + 4: ("wagtail.blocks.CharBlock", (), {"required": True}), + 5: ( + "wagtail.blocks.URLBlock", + (), + {"help_text": "External URL to link to", "required": False}, + ), + 6: ( + "wagtail.blocks.PageChooserBlock", + (), + {"help_text": "Internal page to link to", "required": False}, + ), + 7: ( + "wagtail.blocks.StructBlock", + [[("text", 4), ("url", 5), ("page", 6)]], + {}, + ), + }, + ), + ), + migrations.AddField( + model_name="newsletterpage", + name="newsletter_campaign", + field=models.CharField(blank=True, max_length=1000), + ), + migrations.AddField( + model_name="newsletterpage", + name="newsletter_recipients", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="wagtail_newsletter.newsletterrecipients", + ), + ), + migrations.AddField( + model_name="newsletterpage", + name="newsletter_subject", + field=models.CharField( + blank=True, + help_text="Subject for the newsletter. Defaults to page title if blank.", + max_length=1000, + ), + ), + migrations.AlterField( + model_name="newsletterpage", + name="date", + field=models.DateField(), + ), + ] diff --git a/wagtailio/newsletter/migrations/0005_wagtail_newsletter_data.py b/wagtailio/newsletter/migrations/0005_wagtail_newsletter_data.py new file mode 100644 index 000000000..360436a51 --- /dev/null +++ b/wagtailio/newsletter/migrations/0005_wagtail_newsletter_data.py @@ -0,0 +1,48 @@ +# Generated by Django 5.1.6 on 2025-04-16 13:02 + +import re + +from django.db import migrations + + +def convert_body_to_streamfield(apps, schema_editor): + from bs4 import BeautifulSoup + + NewsletterPage = apps.get_model("newsletter", "NewsletterPage") + for page in NewsletterPage.objects.all(): + soup = BeautifulSoup(page.intro, "html.parser") + preview = soup.get_text(strip=True) + if preview: + page.preview = preview + page.save() + + if page.body: + # Split content on image embeds + pattern = r'(]*embedtype="image"[^>]*id="(?:\d+)"[^>]*>)' + parts = re.split(pattern, page.body) + + content_blocks = [] + + for part in parts: + if part.startswith(""): + # This is an image embed + image_id = re.search(r'id="(\d+)"', part) + if image_id: + content_blocks.append( + {"type": "image", "value": int(image_id.group(1))} + ) + else: + content_blocks.append({"type": "rich_text", "value": part}) + + page.content = content_blocks + page.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("newsletter", "0004_wagtail_newsletter_model"), + ] + + operations = [ + migrations.RunPython(convert_body_to_streamfield, migrations.RunPython.noop), + ] diff --git a/wagtailio/newsletter/migrations/0006_wagtail_newsletter_cleanup.py b/wagtailio/newsletter/migrations/0006_wagtail_newsletter_cleanup.py new file mode 100644 index 000000000..2a84c1516 --- /dev/null +++ b/wagtailio/newsletter/migrations/0006_wagtail_newsletter_cleanup.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.6 on 2025-05-01 13:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("newsletter", "0005_wagtail_newsletter_data"), + ] + + operations = [ + migrations.RemoveField( + model_name="newsletterpage", + name="body", + ), + migrations.RemoveField( + model_name="newsletterpage", + name="intro", + ), + migrations.RenameField( + model_name="newsletterpage", + old_name="content", + new_name="body", + ), + ] diff --git a/wagtailio/newsletter/models.py b/wagtailio/newsletter/models.py index 8e63f0b40..1b0bc3ec7 100644 --- a/wagtailio/newsletter/models.py +++ b/wagtailio/newsletter/models.py @@ -3,19 +3,33 @@ from django.shortcuts import render from wagtail.admin.panels import FieldPanel -from wagtail.fields import RichTextField +from wagtail.contrib.settings.models import BaseGenericSetting, register_setting +from wagtail.fields import RichTextField, StreamField from wagtail.models import Page from wagtail.search import index +from wagtail_newsletter.models import NewsletterPageMixin -class NewsletterPage(Page): - date = models.DateField("Newsletter date") - intro = RichTextField(blank=True) - body = RichTextField() +from wagtailio.newsletter.blocks import NewsletterContentBlock + + +@register_setting +class NewsletterSettings(BaseGenericSetting): + footer = StreamField(NewsletterContentBlock(), blank=True) + + panels = [ + FieldPanel("footer"), + ] + + +class NewsletterPage(NewsletterPageMixin, Page): + date = models.DateField() + preview = models.TextField(blank=True) + body = StreamField(NewsletterContentBlock(), blank=True) content_panels = Page.content_panels + [ FieldPanel("date"), - FieldPanel("intro"), + FieldPanel("preview"), FieldPanel("body"), ] @@ -24,11 +38,23 @@ class NewsletterPage(Page): index.SearchField("body"), ] + newsletter_template = "newsletter/newsletter_mjml.html" + + def get_newsletter_subject(self): + if self.newsletter_subject: + return self.newsletter_subject + + return f"This Week in Wagtail: {self.title}" + + def get_newsletter_context(self): + context = super().get_newsletter_context() + context["newsletter_settings"] = NewsletterSettings.load() + return context + def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) - if request.GET.get("email", "false") == "true": - context["is_email"] = True - + email_html = self.get_newsletter_html(extra_context={"rendering_for_web": True}) + context["email_html"] = email_html return context @@ -36,6 +62,8 @@ class NewsletterIndexPage(Page): intro = RichTextField(blank=True) body = RichTextField() + subpage_types = ["newsletter.NewsletterPage"] + search_fields = Page.search_fields + [ index.SearchField("intro"), index.SearchField("body"), diff --git a/wagtailio/newsletter/templates/newsletter/blocks/accent_rich_text.mjml b/wagtailio/newsletter/templates/newsletter/blocks/accent_rich_text.mjml new file mode 100644 index 000000000..39595dd03 --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/blocks/accent_rich_text.mjml @@ -0,0 +1,15 @@ +{% load wagtail_newsletter %} + + + + + + + + + + + {{ value|newsletter_richtext }} + + + diff --git a/wagtailio/newsletter/templates/newsletter/blocks/button.mjml b/wagtailio/newsletter/templates/newsletter/blocks/button.mjml new file mode 100644 index 000000000..e3fe036c4 --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/blocks/button.mjml @@ -0,0 +1,19 @@ +{% load wagtailcore_tags %} + + + + + {{ value.text }} + + + diff --git a/wagtailio/newsletter/templates/newsletter/blocks/heading.mjml b/wagtailio/newsletter/templates/newsletter/blocks/heading.mjml new file mode 100644 index 000000000..37a2dc8fb --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/blocks/heading.mjml @@ -0,0 +1,9 @@ +{% load wagtailcore_tags %} + + + + +

{{ value }}

+
+
+
diff --git a/wagtailio/newsletter/templates/newsletter/blocks/image.mjml b/wagtailio/newsletter/templates/newsletter/blocks/image.mjml new file mode 100644 index 000000000..e2ce84ea7 --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/blocks/image.mjml @@ -0,0 +1,11 @@ +{% load wagtailcore_tags wagtailimages_tags %} + + + + {% image value width-600 as rendition %} + + + diff --git a/wagtailio/newsletter/templates/newsletter/blocks/rich_text.mjml b/wagtailio/newsletter/templates/newsletter/blocks/rich_text.mjml new file mode 100644 index 000000000..83efcf904 --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/blocks/rich_text.mjml @@ -0,0 +1,9 @@ +{% load wagtail_newsletter %} + + + + + {{ value|newsletter_richtext }} + + + diff --git a/wagtailio/newsletter/templates/newsletter/newsletter_index_page.html b/wagtailio/newsletter/templates/newsletter/newsletter_index_page.html index ce8d71870..e278e2305 100644 --- a/wagtailio/newsletter/templates/newsletter/newsletter_index_page.html +++ b/wagtailio/newsletter/templates/newsletter/newsletter_index_page.html @@ -3,7 +3,7 @@ {% block body_class %}template-newsletter-index-page{% endblock %} -{% block title %}This week in Wagtail{% endblock %} +{% block title %}This Week in Wagtail{% endblock %} {% block content %} @@ -69,7 +69,7 @@

{{ newsletter.title }}

{% if newsletters.has_next %}
- +

Older

diff --git a/wagtailio/newsletter/templates/newsletter/newsletter_mjml.html b/wagtailio/newsletter/templates/newsletter/newsletter_mjml.html new file mode 100644 index 000000000..de4dc2924 --- /dev/null +++ b/wagtailio/newsletter/templates/newsletter/newsletter_mjml.html @@ -0,0 +1,120 @@ +{% load wagtailcore_tags wagtail_newsletter %} + +{% mrml %} + + + + + + + + + + .rich-text p { margin-top: 1.5em } + .rich-text a { color: #007d7e } + h2 { margin: 0; padding: 0; line-height: 1.25; font-weight: bold; font-size: 31px } + .rich-text.rich-text--accent h2 { margin-bottom: 42px } + .rich-text.rich-text--accent a { color: #fff } + .rich-text.rich-text--accent p { margin: 0 } + + + {% if page.preview %} + {{ page.preview }} + {% endif %} + + + + + + + {% if rendering_for_web %} + + {% else %} + View this email online + {% endif %} + + + + + + + + + + + + + + {{ page.date|date:"d F Y" }} + + + + + {% for block in page.body %} + {% include_block block %} + {% endfor %} + + + + + {% for block in newsletter_settings.footer %} + {% include_block block %} + {% endfor %} + + + + + + + + + + + + + + + +

+ {% if rendering_for_web %} + + {% else %} + Want to change how you receive these emails? You can + update your preferences or + unsubscribe from this list. + {% endif %} +

+
+
+
+ + + + + + +
+
+{% endmrml %} diff --git a/wagtailio/newsletter/templates/newsletter/newsletter_page.html b/wagtailio/newsletter/templates/newsletter/newsletter_page.html index 211d0bf17..c50a3c4af 100644 --- a/wagtailio/newsletter/templates/newsletter/newsletter_page.html +++ b/wagtailio/newsletter/templates/newsletter/newsletter_page.html @@ -1,985 +1,11 @@ -{% load static wagtailcore_tags %} - - - - - - {{ page.title }} | This Week in Wagtail - - - - {% if not DEBUG %} - - - {% if not is_email %} - - - - {% endif %} - {% endif %} - - - {% if not DEBUG and not is_email %} - - - - {% endif %} - - - - - -
-
- - - - -
-
- - - - -
- - - {% if is_email %} - - {% endif %} - - -
- View this email online -
-
- - - - -
- - - - - -
- {% wagtail_site as site %} - This Week in Wagtail -
-
-
-
- - - - -
- - - - -
- - - - - -
- {% if page.date %}

{{ page.date|date:"j F Y" }}

{% endif %} - {{ page.body|richtext }} -
-
- - - - -
- - - - - - - - - -
- {% wagtail_site as site %} - Twitter - - {% wagtail_site as site %} - Github - - {% wagtail_site as site %} - Youtube -
-
- - - - -
- - - - - -
-
-
- {% if is_email %} -
- Want to change how you receive these emails?
- You can update your preferences or unsubscribe from this list -
-
- {% endif %} -
-
-
-
-
-
- - +{% block content %} +
+ +
+{% endblock %} diff --git a/wagtailio/settings/base.py b/wagtailio/settings/base.py index 8c0c2e427..fbdfb4dd4 100644 --- a/wagtailio/settings/base.py +++ b/wagtailio/settings/base.py @@ -88,6 +88,7 @@ "wagtailio.areweheadlessyet", "wagtailio.sitewide_alert", "wagtailmedia", + "wagtail_newsletter", "pattern_library", "wagtailio.project_styleguide.apps.ProjectStyleguideConfig", "wagtailfontawesomesvg", @@ -524,7 +525,8 @@ WILLOW_OPTIMIZERS = True if "PRIMARY_HOST" in env: - WAGTAILADMIN_BASE_URL = "https://{}".format(env["PRIMARY_HOST"]) + _protocol = "http" if ":" in env["PRIMARY_HOST"] else "https" + WAGTAILADMIN_BASE_URL = f"{_protocol}://{env['PRIMARY_HOST']}" # https://docs.wagtail.org/en/v2.8.1/releases/2.8.html#responsive-html-for-embeds-no-longer-added-by-default WAGTAILEMBEDS_RESPONSIVE_HTML = True @@ -626,6 +628,20 @@ MAILCHIMP_ACCOUNT_ID = env.get("MAILCHIMP_ACCOUNT_ID") MAILCHIMP_NEWSLETTER_ID = env.get("MAILCHIMP_NEWSLETTER_ID") +if all( + _key in env + for _key in [ + "WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY", + "WAGTAIL_NEWSLETTER_FROM_NAME", + "WAGTAIL_NEWSLETTER_REPLY_TO", + ] +): + WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY = env.get( + "WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY" + ) + WAGTAIL_NEWSLETTER_FROM_NAME = env.get("WAGTAIL_NEWSLETTER_FROM_NAME") + WAGTAIL_NEWSLETTER_REPLY_TO = env.get("WAGTAIL_NEWSLETTER_REPLY_TO") + # all the tracking FB_APP_ID = env.get("FB_APP_ID", "") GOOGLE_TAG_MANAGER_ID = env.get("GOOGLE_TAG_MANAGER_ID", "") diff --git a/wagtailio/static/img/newsletter/bluesky.png b/wagtailio/static/img/newsletter/bluesky.png new file mode 100644 index 000000000..767211f32 Binary files /dev/null and b/wagtailio/static/img/newsletter/bluesky.png differ diff --git a/wagtailio/static/img/newsletter/bluesky.svg b/wagtailio/static/img/newsletter/bluesky.svg new file mode 100644 index 000000000..8fc2ee24b --- /dev/null +++ b/wagtailio/static/img/newsletter/bluesky.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/wagtailio/static/img/newsletter/linkedin.png b/wagtailio/static/img/newsletter/linkedin.png new file mode 100644 index 000000000..c49fdefb0 Binary files /dev/null and b/wagtailio/static/img/newsletter/linkedin.png differ diff --git a/wagtailio/static/img/newsletter/linkedin.svg b/wagtailio/static/img/newsletter/linkedin.svg new file mode 100644 index 000000000..8f06f739a --- /dev/null +++ b/wagtailio/static/img/newsletter/linkedin.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/wagtailio/static/img/newsletter/mastodon.png b/wagtailio/static/img/newsletter/mastodon.png new file mode 100644 index 000000000..47b4708a4 Binary files /dev/null and b/wagtailio/static/img/newsletter/mastodon.png differ diff --git a/wagtailio/static/img/newsletter/mastodon.svg b/wagtailio/static/img/newsletter/mastodon.svg new file mode 100644 index 000000000..3d122bf1e --- /dev/null +++ b/wagtailio/static/img/newsletter/mastodon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/wagtailio/static/img/newsletter/twiw-hero.png b/wagtailio/static/img/newsletter/twiw-hero.png new file mode 100644 index 000000000..45dbf4560 Binary files /dev/null and b/wagtailio/static/img/newsletter/twiw-hero.png differ diff --git a/wagtailio/static/img/newsletter/youtube.png b/wagtailio/static/img/newsletter/youtube.png new file mode 100644 index 000000000..b40c4cda6 Binary files /dev/null and b/wagtailio/static/img/newsletter/youtube.png differ diff --git a/wagtailio/static/img/newsletter/youtube.svg b/wagtailio/static/img/newsletter/youtube.svg new file mode 100644 index 000000000..5b423f4bd --- /dev/null +++ b/wagtailio/static/img/newsletter/youtube.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file