From efd3cbf476866694c17ac70e8622e22f013a10f0 Mon Sep 17 00:00:00 2001 From: Roee Sivan Date: Sun, 25 Oct 2026 16:53:15 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5(backend)=20remove=20easy-thumbnail?= =?UTF-8?q?s=20and=20Pillow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pillow keeps raising security alerts, and the backend only installed it through easy-thumbnails, which nothing uses anymore. Its one direct import was the base64_static template tag, which no template calls, so the extra_tags module goes away too, along with its load in the mail template. No migration drops the easy_thumbnails tables, so existing databases keep them. They are empty, as nothing ever created a thumbnail, so anonymize_database stops truncating them. UPGRADE.md explains how to drop them. Signed-off-by: Roee Sivan --- CHANGELOG.md | 4 ++ UPGRADE.md | 14 +++++ .../management/commands/anonymize_database.py | 4 +- src/backend/core/templatetags/__init__.py | 0 src/backend/core/templatetags/extra_tags.py | 58 ------------------- src/backend/impress/settings.py | 7 --- src/backend/pyproject.toml | 1 - src/backend/uv.lock | 44 -------------- src/mail/mjml/template.mjml | 2 +- 9 files changed, 20 insertions(+), 114 deletions(-) delete mode 100644 src/backend/core/templatetags/__init__.py delete mode 100644 src/backend/core/templatetags/extra_tags.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 237f011d81..6244d0adf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to ## [Unreleased] +### Removed + +- 🔥(backend) remove easy-thumbnails and Pillow #2909 + ## [v6.0.4] - 2026-10-26 ### Changed diff --git a/UPGRADE.md b/UPGRADE.md index abe77a42f0..04693737ea 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -16,6 +16,20 @@ the following command inside your docker container: ## [Unreleased] +- The backend no longer depends on `easy-thumbnails`, nor on Pillow, which + came with it: nothing used them anymore. No migration drops the tables of + `easy-thumbnails`, they stay in the database and the backend reads none of + them: `easy_thumbnails_source`, `easy_thumbnails_thumbnail` and + `easy_thumbnails_thumbnaildimensions`. They are empty: nothing ever + created a thumbnail. Keep them or drop them; to drop them, along with + their entries in `django_migrations`: + + ```sql + DROP TABLE easy_thumbnails_thumbnaildimensions, easy_thumbnails_thumbnail, + easy_thumbnails_source; + DELETE FROM django_migrations WHERE app = 'easy_thumbnails'; + ``` + ## [v6.0.2] - 2026-10-21 - ⚠️ **The collaboration server moves to `@y/hub` 0.10.1: run its schema diff --git a/src/backend/core/management/commands/anonymize_database.py b/src/backend/core/management/commands/anonymize_database.py index 507dc3f815..05e1da14e6 100644 --- a/src/backend/core/management/commands/anonymize_database.py +++ b/src/backend/core/management/commands/anonymize_database.py @@ -13,7 +13,7 @@ (emails, names, the OIDC ``sub``), user-generated content (document titles and excerpts, comment bodies, thread/comment metadata) and secrets (password hashes), and TRUNCATEs the operational tables that embed identity (sessions, -admin log, thumbnail cache). Structural columns that make +admin log). Structural columns that make the data realistic for load testing — the tree ``path``/``depth``/``numchild``, ``link_reach``, roles, foreign keys, row counts, timestamps — are deliberately preserved, so the result pairs naturally with ``profile_volumetry`` / @@ -129,8 +129,6 @@ TRUNCATES = [ "django_session", # session_data can carry the authenticated identity "django_admin_log", # object_repr / change_message leak titles and emails - "easy_thumbnails_source", # thumbnail cache: media file paths - "easy_thumbnails_thumbnail", ] diff --git a/src/backend/core/templatetags/__init__.py b/src/backend/core/templatetags/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/core/templatetags/extra_tags.py b/src/backend/core/templatetags/extra_tags.py deleted file mode 100644 index 273459ff89..0000000000 --- a/src/backend/core/templatetags/extra_tags.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Custom template tags for the core application of Docs.""" - -import base64 - -from django import template -from django.contrib.staticfiles import finders - -from PIL import ImageFile as PillowImageFile - -register = template.Library() - - -def image_to_base64(file_or_path, close=False): - """ - Return the src string of the base64 encoding of an image represented by its path - or file opened or not. - - Inspired by Django's "get_image_dimensions" - """ - pil_parser = PillowImageFile.Parser() - if hasattr(file_or_path, "read"): - file = file_or_path - if file.closed and hasattr(file, "open"): - file_or_path.open() - file_pos = file.tell() - file.seek(0) - else: - try: - # pylint: disable=consider-using-with - file = open(file_or_path, "rb") - except OSError: - return "" - close = True - - try: - image_data = file.read() - if not image_data: - return "" - pil_parser.feed(image_data) - if pil_parser.image: - mime_type = pil_parser.image.get_format_mimetype() - encoded_string = base64.b64encode(image_data) - return f"data:{mime_type:s};base64, {encoded_string.decode('utf-8'):s}" - return "" - finally: - if close: - file.close() - else: - file.seek(file_pos) - - -@register.simple_tag -def base64_static(path): - """Return a static file into a base64.""" - full_path = finders.find(path) - if full_path: - return image_to_base64(full_path, True) - return "" diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 8d07c2dfad..45b91ef243 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -399,7 +399,6 @@ class Base(Configuration): "rest_framework", "parler", "treebeard", - "easy_thumbnails", "waffle", # Django "django.contrib.admin", @@ -704,12 +703,6 @@ class Base(Configuration): "https://eu.i.posthog.com", environ_name="POSTHOG_HOST", environ_prefix=None ) - # Easy thumbnails - THUMBNAIL_EXTENSION = "webp" - THUMBNAIL_TRANSPARENCY_EXTENSION = "webp" - THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default" - THUMBNAIL_ALIASES = {} - # Celery CELERY_BROKER_URL = values.Value("redis://redis:6379/0") CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({}) diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 24bbc3267d..830e5522c7 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -46,7 +46,6 @@ dependencies = [ "django-waffle==5.0.0", "drf_spectacular==0.30.0", "dockerflow==2026.3.4", - "easy_thumbnails==2.10.1", "emoji==2.16.0", "factory_boy==3.3.3", "gunicorn==26.2.0", diff --git a/src/backend/uv.lock b/src/backend/uv.lock index 2e35f1bfe4..3eaf9223e4 100644 --- a/src/backend/uv.lock +++ b/src/backend/uv.lock @@ -737,19 +737,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/09/13509939a12e953759fa2f281b28dc14813c8c331addf3838fccdc71b364/drf_spectacular_sidecar-2026.10.1-py3-none-any.whl", hash = "sha256:fe1d005d8ac9b5d0472f2fe5deeb2e7d6dcffaa24118044f22578b4b8c4b31f2", size = 2656316, upload-time = "2026-10-01T17:20:33.813Z" }, ] -[[package]] -name = "easy-thumbnails" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, - { name = "pillow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/2f/71f17c3ce65b25dd8fc8e513738c8496eadbd8bab94f39509a511ff99704/easy_thumbnails-2.10.1.tar.gz", hash = "sha256:a50aa5f99387546c35ab5ba1ea9b3cbbc5658e65601cd34949f62137c32c222e", size = 81446, upload-time = "2025-08-17T20:49:11.308Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/90/be137d0592501ec08d4a00c8430121e95dfe6c8114a1d4436b5cdd6b9803/easy_thumbnails-2.10.1-py3-none-any.whl", hash = "sha256:24462d63dd31543ef1585538b2bfefe0db96d3409bb431c70b81548fb2cfc5be", size = 79695, upload-time = "2025-08-17T20:49:09.119Z" }, -] - [[package]] name = "emoji" version = "2.16.0" @@ -994,7 +981,6 @@ dependencies = [ { name = "djangorestframework" }, { name = "dockerflow" }, { name = "drf-spectacular" }, - { name = "easy-thumbnails" }, { name = "emoji" }, { name = "factory-boy" }, { name = "gunicorn" }, @@ -1070,7 +1056,6 @@ requires-dist = [ { name = "dockerflow", specifier = "==2026.3.4" }, { name = "drf-spectacular", specifier = "==0.30.0" }, { name = "drf-spectacular-sidecar", marker = "extra == 'dev'", specifier = "==2026.10.1" }, - { name = "easy-thumbnails", specifier = "==2.10.1" }, { name = "emoji", specifier = "==2.16.0" }, { name = "factory-boy", specifier = "==3.3.3" }, { name = "freezegun", marker = "extra == 'dev'", specifier = "==1.5.5" }, @@ -1701,35 +1686,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] -[[package]] -name = "pillow" -version = "13.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/a4/9cc30690a394a8305e8be4c9036aa16d0dfddadd09079860b582f562496d/pillow-13.0.0.tar.gz", hash = "sha256:cdcd1ba258178f7356d74170c9a81ba4eab6a5d55748bc402a5a2a6fe6be3510", size = 47058631, upload-time = "2026-10-16T22:18:09.718Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/45/fbad9c7f5e2f1745c373a434d46072bc84eddfefaa28b852425ac957fda6/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:44629d4498c9cb6b2051806155f22141df0d8a929c56134b166655232dfb2f8c", size = 4190721, upload-time = "2026-10-16T22:16:27.524Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/2074902a259a90692c5dbf132885d21671644cbbd3f690047516243a55db/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bf97ba1b5542050e9b0c74e632323b6d8136cc99d64e193abebee22fe6013c76", size = 4290236, upload-time = "2026-10-16T22:16:29.562Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a5/3d1bb93b8ea38d15d21356c1b8e9f50971ac592aaa248f7fb71739094c0e/pillow-13.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aa694c49787040943570d61c9b8fc01271bd2a2cfdd55f0d896f8aecdce1a981", size = 3723093, upload-time = "2026-10-16T22:16:31.249Z" }, - { url = "https://files.pythonhosted.org/packages/84/a1/be060135d85187b25fa8b2573786c0e45c62ae27527fff5257675eb97f4a/pillow-13.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:49c12af515073e9bf511e4ef736743bf70d531a643668722279314b937742ba4", size = 5572253, upload-time = "2026-10-16T22:16:33.103Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f4/0bf6f1ac9ba7ee2ffe9914aeb410a1035a0c1a3c42f122388d7fc950bc4b/pillow-13.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9b0a2bee3f3e03e9f29057aedb807dbf1e1d96ee1b9d0f422d9db6c8583271d", size = 4852269, upload-time = "2026-10-16T22:16:35.102Z" }, - { url = "https://files.pythonhosted.org/packages/83/16/b603274617b3e6728da3a0133d4fba1ea8d7fbe0c4ea5fef9cbffa398b8b/pillow-13.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5933fcea5a5ba64c686f55c886a115cbfd92f2900460784a1e79909fdbafd7b6", size = 6516058, upload-time = "2026-10-16T22:16:37.228Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f2/1371707ce438ba9a43f9d24089828494ec6f9396e28cd7903aa704dd6306/pillow-13.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e91c2b1cbeae768c98752e67e717e283d68dae45717b41f2d91eaee648e37b7d", size = 7142574, upload-time = "2026-10-16T22:16:39.172Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a9/62cf68587f910bc87c5951bcb6e9430c7643a65076adf5f18b4bf350e2bd/pillow-13.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f6299e494e46253d40c34653cac369412f44ca53b5f85d848d3f95b1ed0546c", size = 6614295, upload-time = "2026-10-16T22:16:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c9/9e9b48178649730299be0bafbf5f810a5b6977538f5a92d47e4ba5f5d608/pillow-13.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f9bd7d260d986f3553518c5d723544346fa48031f8aef3265d786aeb59845bc0", size = 7224841, upload-time = "2026-10-16T22:16:43.118Z" }, - { url = "https://files.pythonhosted.org/packages/8f/48/c44e91384b1b6c072a714a63d00608acc16a6a968735e478e4be4384e5ac/pillow-13.0.0-cp314-cp314-win32.whl", hash = "sha256:2e31c4e68cffd7b1f116127626813b82fcba19edf6b940a7b0c266d6484d0055", size = 6508694, upload-time = "2026-10-16T22:16:45.883Z" }, - { url = "https://files.pythonhosted.org/packages/50/e2/7ad17747517e4c813dca78257d6bb593466f8231baf1384bc98d92745edb/pillow-13.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:f7cbb7a64e7ec664775195e4f81923bc8a93d9451b963f844e59118f63147a7a", size = 7281830, upload-time = "2026-10-16T22:16:47.725Z" }, - { url = "https://files.pythonhosted.org/packages/41/9f/e5b51f2ce8ef4563215334035403ce61a244f93a2e65c24a940131618f87/pillow-13.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6aaa7871aebca081eb5c45f83e845bbd9712ae99fa9286dbfdcee835e63f4b4", size = 2695468, upload-time = "2026-10-16T22:16:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/97/73/a53502f731f6c84805a4ce87a8881e86d07a65aee64728b900d112dc703e/pillow-13.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b999816b5850a35ada08443e6e03945f1f6488144a83afe2d37b240a552aafa", size = 5578196, upload-time = "2026-10-16T22:16:51.291Z" }, - { url = "https://files.pythonhosted.org/packages/21/50/7e949959a56b5559c78ef172b70fc6025706acb289f3f648a15329e4fa6f/pillow-13.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:67871a13cf47275201553b536c025da777f997fa562a19eb798af4ad4b97c18f", size = 4857276, upload-time = "2026-10-16T22:16:53.399Z" }, - { url = "https://files.pythonhosted.org/packages/c9/52/43ded0ac08e77e39d17bd6d894162572201b5e8fdc4a022b14c55a1960a4/pillow-13.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ca8f69b7c77fa96dad99f8638c1fc4f1c0bed4acaa1baf7a3c31bdb80a0ab1", size = 6607008, upload-time = "2026-10-16T22:16:55.525Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2e/4a57ed216d5bf877387cba4d44c02aa87abe51ad2e06404aa6b597c4da39/pillow-13.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bce6a29c3898191f4c9998e4d4e8f69790caeabde46e54da4bc235ce5bde683", size = 7224281, upload-time = "2026-10-16T22:16:57.738Z" }, - { url = "https://files.pythonhosted.org/packages/5c/3d/2929b248e135752d63aaeabf53f6de797e905b7b6263f9c1e2adade20aba/pillow-13.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:816752a5ef76a80484c8c441c85593b7ea9304f6231fc5776ad1fbff7e773058", size = 6695600, upload-time = "2026-10-16T22:16:59.842Z" }, - { url = "https://files.pythonhosted.org/packages/bc/33/fb0da992cf084ed1ee6ee05e96b6191e35c351eb707ca2df874feb24ce47/pillow-13.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5bdb38ba220b61b4ec31dd1046dea6b016d3c6850a23f43ed8b58332b2fc668e", size = 7300589, upload-time = "2026-10-16T22:17:01.686Z" }, - { url = "https://files.pythonhosted.org/packages/bb/af/c30dd9ef6a3dce84ecb1b2393cac629047a32e66279056c6cb46c3ffd65c/pillow-13.0.0-cp314-cp314t-win32.whl", hash = "sha256:06903aac074d00c988e77a1fc435286dc498506d4ca8d6f69df9babddf4ecc15", size = 6517204, upload-time = "2026-10-16T22:17:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/d5/ef3a8ae7609c8ab26570abf18194882534b480d245a9148b895a2262c004/pillow-13.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9a635068d9793c7f0d929f39f18b58f1bd86c236e8e7b64167b86af9232ebca0", size = 7286695, upload-time = "2026-10-16T22:17:05.543Z" }, - { url = "https://files.pythonhosted.org/packages/84/3c/0861db0c69a03689ebe3e7599d35668a414deb5aaafd8255215e4e62e3d3/pillow-13.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0d1a9cf7bc6917538b20f8d06b9ab2147e542f68849e96fc58eb66bdf6acc692", size = 2698098, upload-time = "2026-10-16T22:17:07.674Z" }, -] - [[package]] name = "platformdirs" version = "4.9.6" diff --git a/src/mail/mjml/template.mjml b/src/mail/mjml/template.mjml index 97e92ec4bf..6ef0eb545a 100644 --- a/src/mail/mjml/template.mjml +++ b/src/mail/mjml/template.mjml @@ -6,7 +6,7 @@ We load django tags here so they appear in the body of the HTML output. This ensures html-to-text also includes them in the plain text template. --> - {% load i18n static extra_tags %} + {% load i18n static %}