Merge pull request #186 from jedie/tinymce

switched to https://github.com/jazzband/django-tinymce/
main v0.20.1
Jens Diemer 2024-09-05 20:14:56 +02:00 zatwierdzone przez GitHub
commit f013895d10
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
15 zmienionych plików z 112 dodań i 87 usunięć

Wyświetl plik

@ -143,10 +143,12 @@ please use YunoHost ;)
* Remove `poetry`, `pytest` and `devshell`
* Use `pip-tools`, `unittests` and [manage_django_project](https://github.com/jedie/manage_django_project)
### v0.20.0
### v0.20
Because of security reasons, the `ckeditor` package was replaced by `prose-editor`.
In 0.20.1 we switched to https://github.com/jazzband/django-tinymce/ because `prose-editor` has no table support.
## Make new release
We use [cli-base-utilities](https://github.com/jedie/cli-base-utilities#generate-project-history-base-on-git-commitstags) to generate the history in this README.
@ -164,6 +166,8 @@ To make a new release, do this:
[comment]: <> (✂✂✂ auto generated history start ✂✂✂)
* [v0.20.1](https://github.com/jedie/PyInventory/compare/v0.20.0...v0.20.1)
* 2024-09-05 - switched to https://github.com/jazzband/django-tinymce/
* [v0.20.0](https://github.com/jedie/PyInventory/compare/v0.19.3...v0.20.0)
* 2024-09-05 - Replace django-ckeditor with django-prose-editor and fix tests
* 2024-09-05 - Project updates
@ -180,12 +184,12 @@ To make a new release, do this:
* [v0.19.2](https://github.com/jedie/PyInventory/compare/v0.19.1...v0.19.2)
* 2023-08-17 - Bugfix packaging by adding "requests" as normal dependencies
* 2023-08-17 - Bugfix packageing by adding "requests" as normal dependencies
* [v0.19.1](https://github.com/jedie/PyInventory/compare/v0.19.0...v0.19.1)
* 2023-08-17 - Update requirements
* 2023-08-17 - Update from project template
<details><summary>Expand older history entries ...</summary>
* [v0.19.1](https://github.com/jedie/PyInventory/compare/v0.19.0...v0.19.1)
* 2023-08-17 - Update requirements
* 2023-08-17 - Update from project template
* [v0.19.0](https://github.com/jedie/PyInventory/compare/v0.18.1...v0.19.0)
* 2023-07-21 - Update README.md
* 2023-07-21 - Migrate from "poetry-python" to "managed-django-project"

Wyświetl plik

@ -7,5 +7,5 @@
:license: GNU GPL v3 or above, see LICENSE for more details.
"""
__version__ = '0.20.0'
__version__ = '0.20.1'
__author__ = 'Jens Diemer <PyInventory@jensdiemer.de>'

Wyświetl plik

@ -0,0 +1,44 @@
# Generated by Django 5.1.1 on 2024-09-05 17:46
import tinymce.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0013_alter_itemmodel_location'),
]
operations = [
migrations.AlterField(
model_name='itemmodel',
name='description',
field=tinymce.models.HTMLField(
blank=True,
help_text='ItemModel.description.help_text',
null=True,
verbose_name='ItemModel.description.verbose_name',
),
),
migrations.AlterField(
model_name='locationmodel',
name='description',
field=tinymce.models.HTMLField(
blank=True,
help_text='LocationModel.description.help_text',
null=True,
verbose_name='LocationModel.description.verbose_name',
),
),
migrations.AlterField(
model_name='memomodel',
name='memo',
field=tinymce.models.HTMLField(
blank=True,
help_text='MemoModel.description.help_text',
null=True,
verbose_name='MemoModel.description.verbose_name',
),
),
]

Wyświetl plik

@ -3,13 +3,12 @@ from pathlib import Path
import tagulous.models
from bx_django_utils.filename import clean_filename
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_prose_editor.sanitized import SanitizedProseEditorField
from django_tools.model_version_protect.models import VersionProtectBaseModel
from django_tools.serve_media_app.models import user_directory_path
from tinymce.models import HTMLField
from inventory.models.base import BaseItemAttachmentModel, BaseParentTreeModel
from inventory.models.links import BaseLink
@ -47,10 +46,9 @@ class ItemModel(BaseParentTreeModel, VersionProtectBaseModel):
verbose_name=_('ItemModel.producer.verbose_name'),
help_text=_('ItemModel.producer.help_text'),
)
description = SanitizedProseEditorField(
description = HTMLField(
blank=True,
null=True,
config=settings.PROSE_EDITOR_DEFAULT_CONFIG,
verbose_name=_('ItemModel.description.verbose_name'),
help_text=_('ItemModel.description.help_text'),
)

Wyświetl plik

@ -1,7 +1,6 @@
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django_prose_editor.sanitized import SanitizedProseEditorField
from django_tools.model_version_protect.models import VersionProtectBaseModel
from tinymce.models import HTMLField
from inventory.models.base import BaseParentTreeModel
@ -11,10 +10,9 @@ class LocationModel(BaseParentTreeModel, VersionProtectBaseModel):
A Storage for items.
"""
description = SanitizedProseEditorField(
description = HTMLField(
blank=True,
null=True,
config=settings.PROSE_EDITOR_DEFAULT_CONFIG,
verbose_name=_('LocationModel.description.verbose_name'),
help_text=_('LocationModel.description.help_text'),
)

Wyświetl plik

@ -2,13 +2,12 @@ import logging
from pathlib import Path
from bx_django_utils.filename import clean_filename
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_prose_editor.sanitized import SanitizedProseEditorField
from django_tools.model_version_protect.models import VersionProtectBaseModel
from django_tools.serve_media_app.models import user_directory_path
from tinymce.models import HTMLField
from inventory.models.base import BaseMemoAttachmentModel, BaseModel
from inventory.models.links import BaseLink
@ -22,10 +21,9 @@ class MemoModel(BaseModel, VersionProtectBaseModel):
A Memo to hold some information independent of items/location
"""
memo = SanitizedProseEditorField(
memo = HTMLField(
blank=True,
null=True,
config=settings.PROSE_EDITOR_DEFAULT_CONFIG,
verbose_name=_('MemoModel.description.verbose_name'),
help_text=_('MemoModel.description.help_text'),
)

Wyświetl plik

@ -56,7 +56,7 @@ INSTALLED_APPS = [
'bx_django_utils', # https://github.com/boxine/bx_django_utils
'import_export', # https://github.com/django-import-export/django-import-export
'dbbackup', # https://github.com/django-dbbackup/django-dbbackup
'django_prose_editor', # https://github.com/matthiask/django-prose-editor/
'tinymce', # https://github.com/jazzband/django-tinymce/
'reversion', # https://github.com/etianen/django-reversion
'reversion_compare', # https://github.com/jedie/django-reversion-compare
'tagulous', # https://github.com/radiac/django-tagulous
@ -177,16 +177,35 @@ DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
DBBACKUP_STORAGE_OPTIONS = {'location': str(__Path(BASE_PATH, 'backups'))}
# _____________________________________________________________________________
# django-prose-editor
# django-tinymce
PROSE_EDITOR_DEFAULT_CONFIG = {
"types": None, # Allow all nodes and marks
"history": True, # Enable undo and redo
"html": True, # Add a button which allows editing the raw HTML
"typographic": True, # Highlight typographic characters
"headingLevels": [1, 2, 3, 4, 5], # Available heading levels
TINYMCE_DEFAULT_CONFIG = {
# https://www.tiny.cloud/docs/tinymce/latest/editor-size-options/
'height': 500,
'width': '90%',
'resize': 'both',
#
# https://www.tiny.cloud/docs/tinymce/latest/menus-configuration-options/
'menubar': 'edit view insert format tools table help',
'browser_spellcheck': True,
#
# https://www.tiny.cloud/docs/tinymce/latest/plugins/
'plugins': (
'advlist,autolink,lists,link,image,charmap,preview,anchor,'
'searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,'
'help,wordcount'
),
#
# https://www.tiny.cloud/docs/tinymce/latest/available-toolbar-buttons/
'toolbar': (
'undo redo | blocks | '
'bold italic backcolor | alignleft aligncenter '
'alignright alignjustify | bullist numlist outdent indent | '
'removeformat'
),
}
# _____________________________________________________________________________
# http://radiac.net/projects/django-tagulous/documentation/installation/#settings

Wyświetl plik

@ -165,8 +165,7 @@
<label for="id_description">
Description:
</label>
<textarea aria-describedby="id_description_helptext" cols="40" data-django-prose-editor='{"types":null,"history":true,"html":true,"typographic":true,"headingLevels":[1,2,3,4,5]}' id="id_description" name="description" rows="10">
</textarea>
<textarea aria-describedby="id_description_helptext" class="vLargeTextField tinymce" cols="40" data-mce-conf='{"height": 500, "width": "90%", "resize": "both", "menubar": "edit view insert format tools table help", "browser_spellcheck": true, "plugins": "advlist,autolink,lists,link,image,charmap,preview,anchor,searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,help,wordcount", "toolbar": "undo redo | blocks | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat", "language": "en", "directionality": "ltr", "strict_loading_mode": 1, "selector": "#id_description"}' id="id_description" name="description" rows="10"></textarea>
</div>
<div class="help" id="id_description_helptext">
<div>

Wyświetl plik

@ -19,17 +19,14 @@
</script>
<link href="/static/adminsortable2/css/sortable.css" media="all" rel="stylesheet"/>
<link href="/static/admin/css/vendor/select2/select2.min.css" media="screen" rel="stylesheet"/>
<link href="/static/django_prose_editor/material-icons.css" media="screen" rel="stylesheet"/>
<link href="/static/django_prose_editor/overrides.css" media="screen" rel="stylesheet"/>
<link href="/static/admin/css/autocomplete.css" media="screen" rel="stylesheet"/>
<link href="/static/django_prose_editor/editor.css" media="screen" rel="stylesheet"/>
<script src="/static/admin/js/vendor/jquery/jquery.min.js">
</script>
<script src="/static/adminsortable2/js/adminsortable2.min.js">
</script>
<script src="/static/tagulous/tagulous.js">
</script>
<script src="/static/django_prose_editor/editor.js">
<script src="/static/tinymce/tinymce.min.js">
</script>
<script src="/static/admin/js/calendar.js">
</script>
@ -37,7 +34,7 @@
</script>
<script src="/static/tagulous/adaptor/select2-4.js">
</script>
<script data-messages='{"url": "URL", "title": "Title", "update": "Update", "cancel": "Cancel"}' src="/static/django_prose_editor/init.js">
<script src="/static/django_tinymce/init_tinymce.js">
</script>
<script src="/static/admin/js/admin/DateTimeShortcuts.js">
</script>
@ -398,8 +395,7 @@
<label for="id_description">
Description:
</label>
<textarea aria-describedby="id_description_helptext" cols="40" data-django-prose-editor='{"types":null,"history":true,"html":true,"typographic":true,"headingLevels":[1,2,3,4,5]}' id="id_description" name="description" rows="10">
</textarea>
<textarea aria-describedby="id_description_helptext" class="vLargeTextField tinymce" cols="40" data-mce-conf='{"height": 500, "width": "90%", "resize": "both", "menubar": "edit view insert format tools table help", "browser_spellcheck": true, "plugins": "advlist,autolink,lists,link,image,charmap,preview,anchor,searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,help,wordcount", "toolbar": "undo redo | blocks | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat", "language": "en", "directionality": "ltr", "strict_loading_mode": 1, "selector": "#id_description"}' id="id_description" name="description" rows="10"></textarea>
</div>
<div class="help" id="id_description_helptext">
<div>

Wyświetl plik

@ -133,8 +133,7 @@
<label for="id_memo">
Description:
</label>
<textarea aria-describedby="id_memo_helptext" cols="40" data-django-prose-editor='{"types":null,"history":true,"html":true,"typographic":true,"headingLevels":[1,2,3,4,5]}' id="id_memo" name="memo" rows="10">
</textarea>
<textarea aria-describedby="id_memo_helptext" class="vLargeTextField tinymce" cols="40" data-mce-conf='{"height": 500, "width": "90%", "resize": "both", "menubar": "edit view insert format tools table help", "browser_spellcheck": true, "plugins": "advlist,autolink,lists,link,image,charmap,preview,anchor,searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,help,wordcount", "toolbar": "undo redo | blocks | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat", "language": "en", "directionality": "ltr", "strict_loading_mode": 1, "selector": "#id_memo"}' id="id_memo" name="memo" rows="10"></textarea>
</div>
<div class="help" id="id_memo_helptext">
<div>

Wyświetl plik

@ -1,24 +1,23 @@
from django.forms import modelform_factory
from django.forms import CharField, modelform_factory
from django.test import TestCase
from django_prose_editor.fields import ProseEditorFormField
from django_prose_editor.sanitized import SanitizedProseEditorField
from django_prose_editor.widgets import ProseEditorWidget
from tinymce.models import HTMLField
from tinymce.widgets import TinyMCE
from inventory.models import ItemModel
class ItemModelTestCase(TestCase):
def test_item_description_prose_editor(self):
def test_item_description_model_field(self):
item = ItemModel()
opts = item._meta
model_description_field = opts.get_field('description')
self.assertIsInstance(model_description_field, SanitizedProseEditorField)
self.assertIsInstance(model_description_field, HTMLField)
def test_item_description_form_prose_editor(self):
def test_item_description_form_fieldr(self):
ItemForm = modelform_factory(ItemModel, fields=('description',))
form = ItemForm()
form_field = form.fields['description']
self.assertIsInstance(form_field, ProseEditorFormField)
self.assertIsInstance(form_field, CharField)
widget = form_field.widget
self.assertIsInstance(widget, ProseEditorWidget)
self.assertIsInstance(widget, TinyMCE)

Wyświetl plik

@ -10,6 +10,7 @@ admin.autodiscover()
urlpatterns = [ # Don't use i18n_patterns() here
path('admin/', admin.site.urls),
re_path(r'^$', RedirectView.as_view(pattern_name='admin:index')),
path('tinymce/', include('tinymce.urls')), # TODO: check permissions?
path(settings.MEDIA_URL.lstrip('/'), include('django_tools.serve_media_app.urls')),
]

Wyświetl plik

@ -20,7 +20,7 @@ dependencies = [
"django-dbbackup", # https://github.com/django-dbbackup/django-dbbackup
"django-tools", # https://github.com/jedie/django-tools/
"django-reversion-compare", # https://github.com/jedie/django-reversion-compare/
"django-prose-editor[sanitize]", # https://github.com/matthiask/django-prose-editor/
"django-tinymce", # https://github.com/jazzband/django-tinymce/
"django-tagulous", # https://github.com/radiac/django-tagulous
"django-admin-sortable2", # https://github.com/jrief/django-admin-sortable2
"pillow", # https://github.com/jrief/django-admin-sortable2

Wyświetl plik

@ -473,12 +473,11 @@ django==5.1.1 \
# django-dbbackup
# django-debug-toolbar
# django-import-export
# django-js-asset
# django-prose-editor
# django-reversion
# django-reversion-compare
# django-rich
# django-tagulous
# django-tinymce
# django-tools
# manage-django-project
# model-bakery
@ -498,18 +497,10 @@ django-import-export==4.1.1 \
--hash=sha256:16ecc5a9f0df46bde6eb278a3e65ebda0ee1db55656f36440e9fb83f40ab85a3 \
--hash=sha256:730ae2443a02b1ba27d8dba078a27ae9123adfcabb78161b4f130843607b3df9
# via PyInventory (pyproject.toml)
django-js-asset==2.2.0 \
--hash=sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94 \
--hash=sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8
# via django-prose-editor
django-override-storage==0.3.2 \
--hash=sha256:1f1a13274d66cc481b19d63c8bd43c94066824008bcdd26ec65d125b1ce8ec39 \
--hash=sha256:995e1a42f056c9f9bc114077c11d67520ec7d8a752a59be62729e641562b133e
# via PyInventory (pyproject.toml)
django-prose-editor==0.8.0 \
--hash=sha256:b3924d489c7ae37f3ce856150afdbaf034ef11cee149af236c5204462577665c \
--hash=sha256:e0313a26be6bd9db313d5106c6c0dac78bb75736d7a987302215363c6eec596e
# via PyInventory (pyproject.toml)
django-reversion==5.1.0 \
--hash=sha256:084d4f117d9e2b4e8dfdfaad83ebb34410a03eed6071c96089e6811fdea82ad3 \
--hash=sha256:3309821e5b6fceedcce6b6975f1a9c7fab6ae7c7d0e1276a90e345946fa0dcb8
@ -526,6 +517,10 @@ django-tagulous==2.1.0 \
--hash=sha256:5ebba5a51f049f6df5f9d2a30eef431c0bf7cd35758aa0a42fc3351be4d239cd \
--hash=sha256:f629b54ad720052092785b0dce056dc6a68c7b63f8126075af9c25848b250bfd
# via PyInventory (pyproject.toml)
django-tinymce==4.1.0 \
--hash=sha256:02e3b70e940fd299f0fbef4315aee5c185664e1eb8cd396b176963954e4357c9 \
--hash=sha256:9804836e6d2b08de3b03a27c100f8c2e9633549913eff8b323678a10cd48b94e
# via PyInventory (pyproject.toml)
django-tools==0.56.2 \
--hash=sha256:29c25be814d74cd9f554d7d45bc205f5570e5feaa4232cbd09cc913c46b20c07 \
--hash=sha256:88a192f2873f0411b99ee1aba04f2779133284cd18a5c78976e8e4605ba5d7f7
@ -1034,9 +1029,7 @@ nh3==0.2.18 \
--hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \
--hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \
--hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe
# via
# django-prose-editor
# readme-renderer
# via readme-renderer
nodeenv==1.9.1 \
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9

Wyświetl plik

@ -143,11 +143,10 @@ django==5.1.1 \
# django-dbbackup
# django-debug-toolbar
# django-import-export
# django-js-asset
# django-prose-editor
# django-reversion
# django-reversion-compare
# django-tagulous
# django-tinymce
# django-tools
django-admin-sortable2==2.2.2 \
--hash=sha256:efb28eed633e3a008c6938a887096a9887a213628a39458dc748d654f8f12d5c \
@ -165,14 +164,6 @@ django-import-export==4.1.1 \
--hash=sha256:16ecc5a9f0df46bde6eb278a3e65ebda0ee1db55656f36440e9fb83f40ab85a3 \
--hash=sha256:730ae2443a02b1ba27d8dba078a27ae9123adfcabb78161b4f130843607b3df9
# via PyInventory (pyproject.toml)
django-js-asset==2.2.0 \
--hash=sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94 \
--hash=sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8
# via django-prose-editor
django-prose-editor==0.8.0 \
--hash=sha256:b3924d489c7ae37f3ce856150afdbaf034ef11cee149af236c5204462577665c \
--hash=sha256:e0313a26be6bd9db313d5106c6c0dac78bb75736d7a987302215363c6eec596e
# via PyInventory (pyproject.toml)
django-reversion==5.1.0 \
--hash=sha256:084d4f117d9e2b4e8dfdfaad83ebb34410a03eed6071c96089e6811fdea82ad3 \
--hash=sha256:3309821e5b6fceedcce6b6975f1a9c7fab6ae7c7d0e1276a90e345946fa0dcb8
@ -185,6 +176,10 @@ django-tagulous==2.1.0 \
--hash=sha256:5ebba5a51f049f6df5f9d2a30eef431c0bf7cd35758aa0a42fc3351be4d239cd \
--hash=sha256:f629b54ad720052092785b0dce056dc6a68c7b63f8126075af9c25848b250bfd
# via PyInventory (pyproject.toml)
django-tinymce==4.1.0 \
--hash=sha256:02e3b70e940fd299f0fbef4315aee5c185664e1eb8cd396b176963954e4357c9 \
--hash=sha256:9804836e6d2b08de3b03a27c100f8c2e9633549913eff8b323678a10cd48b94e
# via PyInventory (pyproject.toml)
django-tools==0.56.2 \
--hash=sha256:29c25be814d74cd9f554d7d45bc205f5570e5feaa4232cbd09cc913c46b20c07 \
--hash=sha256:88a192f2873f0411b99ee1aba04f2779133284cd18a5c78976e8e4605ba5d7f7
@ -211,24 +206,6 @@ mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
# via markdown-it-py
nh3==0.2.18 \
--hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \
--hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \
--hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \
--hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \
--hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \
--hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \
--hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \
--hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \
--hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \
--hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \
--hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \
--hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \
--hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \
--hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \
--hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \
--hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe
# via django-prose-editor
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124