diff --git a/requirements/base.in b/requirements/base.in index 4ecf1c7..6526623 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -8,6 +8,6 @@ django-treebeard # API libraries # django-extra-fields - +django-tinymce django-localflavor diff --git a/requirements/base.txt b/requirements/base.txt index ca90f2b..eb4e685 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -92,6 +92,7 @@ django==4.2.13 # django-setup-configuration # django-simple-certmanager # django-solo + # django-tinymce # django-treebeard # django-two-factor-auth # djangorestframework @@ -156,6 +157,8 @@ django-solo==2.3.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-tinymce==4.1.0 + # via -r requirements/base.in django-treebeard==4.7.1 # via -r requirements/base.in django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 diff --git a/requirements/ci.txt b/requirements/ci.txt index fb4baa6..7b5d892 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -163,6 +163,7 @@ django==4.2.13 # django-setup-configuration # django-simple-certmanager # django-solo + # django-tinymce # django-treebeard # django-two-factor-auth # djangorestframework @@ -286,6 +287,10 @@ django-solo==2.3.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-tinymce==4.1.0 + # via + # -c requirements/base.txt + # -r requirements/base.txt django-treebeard==4.7.1 # via # -c requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 48c346f..22df079 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -193,6 +193,7 @@ django==4.2.13 # django-setup-configuration # django-simple-certmanager # django-solo + # django-tinymce # django-treebeard # django-two-factor-auth # djangorestframework @@ -320,6 +321,10 @@ django-solo==2.3.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-tinymce==4.1.0 + # via + # -c requirements/ci.txt + # -r requirements/ci.txt django-treebeard==4.7.1 # via # -c requirements/ci.txt diff --git a/src/open_producten/conf/base.py b/src/open_producten/conf/base.py index 1d9095a..b6c8682 100644 --- a/src/open_producten/conf/base.py +++ b/src/open_producten/conf/base.py @@ -16,6 +16,7 @@ "rest_framework.authtoken", "localflavor", "treebeard", + "tinymce", "open_producten.accounts", "open_producten.utils", "open_producten.producttypes", @@ -90,3 +91,17 @@ "TOS": None, "VERSION": API_VERSION, } + +TINYMCE_DEFAULT_CONFIG = { # TODO: light/dark mode based on browser settings + "height": 200, + "menubar": False, + "plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor," + "searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste," + "code,wordcount", + "toolbar": "undo redo | formatselect | " + "bold italic backcolor | alignleft aligncenter " + "alignright alignjustify | bullist numlist outdent indent | " + "removeformat", + "skin": "oxide-dark", + "content_css": "dark", +} diff --git a/src/open_producten/producttypes/migrations/0007_alter_category_description_and_more.py b/src/open_producten/producttypes/migrations/0007_alter_category_description_and_more.py new file mode 100644 index 0000000..4e2fad8 --- /dev/null +++ b/src/open_producten/producttypes/migrations/0007_alter_category_description_and_more.py @@ -0,0 +1,80 @@ +# Generated by Django 4.2.13 on 2024-10-11 08:07 + +from django.db import migrations +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("producttypes", "0006_remove_uniformproductname_url_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="category", + name="description", + field=tinymce.models.HTMLField( + blank=True, + default="", + help_text="Description of the category", + verbose_name="Description", + ), + ), + migrations.AlterField( + model_name="condition", + name="negative_text", + field=tinymce.models.HTMLField( + help_text="Description how not to meet the condition", + verbose_name="Negative text", + ), + ), + migrations.AlterField( + model_name="condition", + name="positive_text", + field=tinymce.models.HTMLField( + help_text="Description how to meet the condition", + verbose_name="Positive text", + ), + ), + migrations.AlterField( + model_name="condition", + name="question", + field=tinymce.models.HTMLField( + help_text="Question used in the question-answer game", + verbose_name="Question", + ), + ), + migrations.AlterField( + model_name="field", + name="description", + field=tinymce.models.HTMLField( + help_text="Short description of the field", + max_length=300, + verbose_name="Description", + ), + ), + migrations.AlterField( + model_name="producttype", + name="content", + field=tinymce.models.HTMLField( + help_text="Product type content with build-in WYSIWYG editor.", + verbose_name="Content", + ), + ), + migrations.AlterField( + model_name="producttype", + name="summary", + field=tinymce.models.HTMLField( + default="", + help_text="Short description of the product type, limited to 300 characters.", + max_length=300, + verbose_name="Summary", + ), + ), + migrations.AlterField( + model_name="question", + name="answer", + field=tinymce.models.HTMLField(verbose_name="Answer"), + ), + ] diff --git a/src/open_producten/producttypes/models/category.py b/src/open_producten/producttypes/models/category.py index 028c339..bbcc462 100644 --- a/src/open_producten/producttypes/models/category.py +++ b/src/open_producten/producttypes/models/category.py @@ -2,6 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from tinymce import models as tinymce_models from treebeard.exceptions import InvalidMoveToDescendant from treebeard.mp_tree import MP_MoveHandler, MP_Node @@ -22,7 +23,7 @@ class Category(MP_Node, BasePublishableModel): verbose_name=_("Name"), max_length=100, help_text=_("Name of the category") ) - description = models.TextField( + description = tinymce_models.HTMLField( verbose_name=_("Description"), blank=True, default="", diff --git a/src/open_producten/producttypes/models/condition.py b/src/open_producten/producttypes/models/condition.py index 5961c95..a3e100c 100644 --- a/src/open_producten/producttypes/models/condition.py +++ b/src/open_producten/producttypes/models/condition.py @@ -1,6 +1,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from tinymce import models as tinymce_models + from open_producten.utils.models import BaseModel @@ -10,15 +12,15 @@ class Condition(BaseModel): max_length=100, help_text=_("Short name of the condition"), ) - question = models.TextField( + question = tinymce_models.HTMLField( verbose_name=_("Question"), help_text=_("Question used in the question-answer game"), ) - positive_text = models.TextField( + positive_text = tinymce_models.HTMLField( verbose_name=_("Positive text"), help_text=_("Description how to meet the condition"), ) - negative_text = models.TextField( + negative_text = tinymce_models.HTMLField( verbose_name=_("Negative text"), help_text=_("Description how not to meet the condition"), ) diff --git a/src/open_producten/producttypes/models/field.py b/src/open_producten/producttypes/models/field.py index 8c685f1..4625784 100644 --- a/src/open_producten/producttypes/models/field.py +++ b/src/open_producten/producttypes/models/field.py @@ -3,6 +3,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from tinymce import models as tinymce_models + from open_producten.utils.models import BaseModel from .producttype import ProductType @@ -37,7 +39,7 @@ class Field(BaseModel): name = models.CharField( verbose_name=_("Name"), max_length=255, help_text=_("The name of the field") ) - description = models.TextField( + description = tinymce_models.HTMLField( verbose_name=_("Description"), help_text=_("Short description of the field"), max_length=300, diff --git a/src/open_producten/producttypes/models/producttype.py b/src/open_producten/producttypes/models/producttype.py index f8e0a15..2ba3a24 100644 --- a/src/open_producten/producttypes/models/producttype.py +++ b/src/open_producten/producttypes/models/producttype.py @@ -4,6 +4,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from tinymce import models as tinymce_models + from open_producten.locations.models import Contact, Location, Organisation from open_producten.utils.models import BasePublishableModel @@ -12,6 +14,8 @@ from .tag import Tag from .upn import UniformProductName +# from ...utils.models import HTMLField + class CategoryProductType(models.Model): """ @@ -27,7 +31,7 @@ class ProductType(BasePublishableModel): verbose_name=_("Name"), max_length=100, help_text=_("Name of the product type") ) - summary = models.TextField( + summary = tinymce_models.HTMLField( verbose_name=_("Summary"), default="", max_length=300, @@ -57,7 +61,7 @@ class ProductType(BasePublishableModel): help_text=_("Action link to request the product type."), ) - content = models.TextField( + content = tinymce_models.HTMLField( verbose_name=_("Content"), help_text=_("Product type content with build-in WYSIWYG editor."), ) diff --git a/src/open_producten/producttypes/models/question.py b/src/open_producten/producttypes/models/question.py index bf91e0f..f9627ff 100644 --- a/src/open_producten/producttypes/models/question.py +++ b/src/open_producten/producttypes/models/question.py @@ -2,6 +2,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from tinymce import models as tinymce_models + from open_producten.utils.models import BaseModel from .category import Category @@ -26,7 +28,7 @@ class Question(BaseModel): related_name="questions", ) question = models.CharField(verbose_name=_("Question"), max_length=250) - answer = models.TextField(verbose_name=_("Answer")) + answer = tinymce_models.HTMLField(verbose_name=_("Answer")) class Meta: verbose_name = _("Question") diff --git a/src/open_producten/urls.py b/src/open_producten/urls.py index 1376961..d61f02d 100644 --- a/src/open_producten/urls.py +++ b/src/open_producten/urls.py @@ -83,6 +83,7 @@ ] ), ), + path("tinymce/", include("tinymce.urls")), ] # NOTE: The staticfiles_urlpatterns also discovers static files (ie. no need to run collectstatic). Both the static